Manual pages: mcmcdiffmceditmcview

root/src/filemanager/mountlist.c

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

DEFINITIONS

This source file includes following definitions.
  1. me_remote
  2. statvfs_works
  3. free_mount_entry
  4. fstype_to_string
  5. fsp_to_string
  6. fstype_to_string
  7. dev_from_mount_options
  8. unescape_tab
  9. read_file_system_list
  10. read_file_system_list
  11. get_fs_usage
  12. free_my_statfs
  13. init_my_statfs
  14. my_statfs

   1 /*
   2    Return a list of mounted file systems
   3 
   4    Copyright (C) 1991-2026
   5    Free Software Foundation, Inc.
   6 
   7    This file is part of the Midnight Commander.
   8 
   9    The Midnight Commander is free software: you can redistribute it
  10    and/or modify it under the terms of the GNU General Public License as
  11    published by the Free Software Foundation, either version 3 of the License,
  12    or (at your option) any later version.
  13 
  14    The Midnight Commander is distributed in the hope that it will be useful,
  15    but WITHOUT ANY WARRANTY; without even the implied warranty of
  16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  17    GNU General Public License for more details.
  18 
  19    You should have received a copy of the GNU General Public License
  20    along with this program.  If not, see <https://www.gnu.org/licenses/>.
  21  */
  22 
  23 /** \file mountlist.c
  24  *  \brief Source: list of mounted filesystems
  25  */
  26 
  27 #include <config.h>
  28 
  29 #include <limits.h>
  30 #include <stdio.h>
  31 #include <stdlib.h>
  32 #include <string.h>
  33 #include <stdint.h>  // SIZE_MAX
  34 #include <sys/types.h>
  35 
  36 #include <errno.h>
  37 
  38 /* This header needs to be included before sys/mount.h on *BSD */
  39 #ifdef HAVE_SYS_PARAM_H
  40 #include <sys/param.h>
  41 #endif
  42 
  43 #if defined STAT_STATVFS || defined STAT_STATVFS64  // POSIX 1003.1-2001 (and later) with XSI
  44 #include <sys/statvfs.h>
  45 #else
  46 /* Don't include backward-compatibility files unless they're needed.
  47    Eventually we'd like to remove all this cruft.  */
  48 #include <fcntl.h>
  49 #include <unistd.h>
  50 #include <sys/stat.h>
  51 
  52 #ifdef MOUNTED_GETFSSTAT  // (obsolete) Apple Darwin 1.3
  53 #ifdef HAVE_SYS_UCRED_H
  54 #include <grp.h>        /* needed on OSF V4.0 for definition of NGROUPS,
  55                                    NGROUPS is used as an array dimension in ucred.h */
  56 #include <sys/ucred.h>  // needed by powerpc-apple-darwin1.3.7
  57 #endif
  58 #ifdef HAVE_SYS_MOUNT_H
  59 #include <sys/mount.h>
  60 #endif
  61 #ifdef HAVE_SYS_FS_TYPES_H
  62 #include <sys/fs_types.h>  // needed by powerpc-apple-darwin1.3.7
  63 #endif
  64 #ifdef HAVE_STRUCT_FSSTAT_F_FSTYPENAME
  65 #define FS_TYPE(Ent) ((Ent).f_fstypename)
  66 #else
  67 #define FS_TYPE(Ent) mnt_names[(Ent).f_type]
  68 #endif
  69 #endif
  70 #endif
  71 
  72 #ifdef HAVE_SYS_VFS_H
  73 #include <sys/vfs.h>
  74 #endif
  75 #ifdef HAVE_SYS_FS_S5PARAM_H  // Fujitsu UXP/V
  76 #include <sys/fs/s5param.h>
  77 #endif
  78 #ifdef HAVE_SYS_STATFS_H
  79 #include <sys/statfs.h>
  80 #endif
  81 
  82 #ifdef MOUNTED_GETMNTENT1 /* glibc, HP-UX, Cygwin, Android,                                        \
  83                              also (obsolete) 4.3BSD, SunOS */
  84 #include <mntent.h>
  85 #include <sys/types.h>
  86 #if defined __ANDROID__  // Android
  87 /* Bionic versions from between 2014-01-09 and 2015-01-08 define MOUNTED to
  88    an incorrect value; older Bionic versions don't define it at all.  */
  89 #undef MOUNTED
  90 #define MOUNTED "/proc/mounts"
  91 #elif !defined MOUNTED
  92 #ifdef _PATH_MOUNTED  // GNU libc
  93 #define MOUNTED _PATH_MOUNTED
  94 #endif
  95 #ifdef MNT_MNTTAB  // HP-UX.
  96 #define MOUNTED MNT_MNTTAB
  97 #endif
  98 #endif
  99 #endif
 100 
 101 #ifdef MOUNTED_GETMNTINFO  // Mac OS X, FreeBSD, OpenBSD, also (obsolete) 4.4BSD
 102 #include <sys/mount.h>
 103 #endif
 104 
 105 #ifdef MOUNTED_GETMNTINFO2  // NetBSD, Minix
 106 #include <sys/statvfs.h>
 107 #endif
 108 
 109 #ifdef MOUNTED_FS_STAT_DEV  // Haiku, also (obsolete) BeOS
 110 #include <fs_info.h>
 111 #include <dirent.h>
 112 #endif
 113 
 114 #ifdef MOUNTED_FREAD_FSTYP  // (obsolete) SVR3
 115 #include <mnttab.h>
 116 #include <sys/fstyp.h>
 117 #include <sys/statfs.h>
 118 #endif
 119 
 120 #ifdef MOUNTED_GETEXTMNTENT  // Solaris >= 8
 121 #include <sys/mnttab.h>
 122 #endif
 123 
 124 #ifdef MOUNTED_GETMNTENT2  // Solaris < 8, also (obsolete) SVR4
 125 #include <sys/mnttab.h>
 126 #endif
 127 
 128 #ifdef MOUNTED_VMOUNT  // AIX
 129 #include <fshelp.h>
 130 #include <sys/vfs.h>
 131 #endif
 132 
 133 #ifdef MOUNTED_INTERIX_STATVFS  // Interix
 134 #include <sys/statvfs.h>
 135 #include <dirent.h>
 136 #endif
 137 
 138 #ifdef HAVE_SYS_MNTENT_H
 139 /* This is to get MNTOPT_IGNORE on e.g. SVR4.  */
 140 #include <sys/mntent.h>
 141 #endif
 142 
 143 #ifdef MOUNTED_GETMNTENT1
 144 #if !HAVE_SETMNTENT  // Android <= 4.4
 145 #define setmntent(fp, mode) fopen (fp, mode)
 146 #endif
 147 #if !HAVE_ENDMNTENT  // Android <= 4.4
 148 #define endmntent(fp) fclose (fp)
 149 #endif
 150 #endif
 151 
 152 #if defined _WIN32 && !defined __CYGWIN__
 153 #include <windows.h>
 154 #endif
 155 
 156 #ifndef HAVE_HASMNTOPT
 157 #define hasmntopt(mnt, opt) ((char *) 0)
 158 #endif
 159 
 160 #undef MNT_IGNORE
 161 #ifdef MNTOPT_IGNORE
 162 #if defined __sun && defined __SVR4
 163 /* Solaris defines hasmntopt(struct mnttab *, char *)
 164    while it is otherwise hasmntopt(struct mnttab *, const char *).  */
 165 #define MNT_IGNORE(M) hasmntopt (M, (char *) MNTOPT_IGNORE)
 166 #else
 167 #define MNT_IGNORE(M) hasmntopt (M, MNTOPT_IGNORE)
 168 #endif
 169 #else
 170 #define MNT_IGNORE(M) 0
 171 #endif
 172 
 173 #ifdef HAVE_INFOMOUNT_QNX
 174 #include <sys/disk.h>
 175 #include <sys/fsys.h>
 176 #endif
 177 
 178 #ifdef HAVE_SYS_STATVFS_H  // SVR4.
 179 #include <sys/statvfs.h>
 180 #endif
 181 
 182 #include "lib/global.h"
 183 #include "lib/strutil.h"     // str_verscmp()
 184 #include "lib/unixcompat.h"  // makedev
 185 #include "mountlist.h"
 186 
 187 /*** global variables ****************************************************************************/
 188 
 189 /*** file scope macro definitions ****************************************************************/
 190 
 191 #if defined(__QNX__) && !defined(__QNXNTO__) && !defined(HAVE_INFOMOUNT_LIST)
 192 #define HAVE_INFOMOUNT_QNX
 193 #endif
 194 
 195 #if defined(HAVE_INFOMOUNT_LIST) || defined(HAVE_INFOMOUNT_QNX)
 196 #define HAVE_INFOMOUNT
 197 #endif
 198 
 199 /* The results of opendir() in this file are not used with dirfd and fchdir,
 200    therefore save some unnecessary work in fchdir.c.  */
 201 #undef opendir
 202 #undef closedir
 203 
 204 // clang-format off
 205 #define ME_DUMMY_0(Fs_name, Fs_type)                                                               \
 206     (strcmp (Fs_type, "autofs") == 0                                                               \
 207     || strcmp (Fs_type, "proc") == 0                                                               \
 208     || strcmp (Fs_type, "subfs") == 0                                                              \
 209     /* for Linux 2.6/3.x */                                                                        \
 210     || strcmp (Fs_type, "debugfs") == 0                                                            \
 211     || strcmp (Fs_type, "devpts") == 0                                                             \
 212     || strcmp (Fs_type, "fusectl") == 0                                                            \
 213     || strcmp (Fs_type, "fuse.portal") == 0                                                        \
 214     || strcmp (Fs_type, "mqueue") == 0                                                             \
 215     || strcmp (Fs_type, "rpc_pipefs") == 0                                                         \
 216     || strcmp (Fs_type, "sysfs") == 0                                                              \
 217     /* FreeBSD, Linux 2.4 */                                                                       \
 218     || strcmp (Fs_type, "devfs") == 0                                                              \
 219     /* for NetBSD 3.0 */                                                                           \
 220     || strcmp (Fs_type, "kernfs") == 0)
 221 // clang-format on
 222 
 223 /* Historically, we have marked as "dummy" any file system of type "none",
 224    but now that programs like du need to know about bind-mounted directories,
 225    we grant an exception to any with "bind" in its list of mount options.
 226    I.e., those are *not* dummy entries.  */
 227 #ifdef MOUNTED_GETMNTENT1
 228 #define ME_DUMMY(Fs_name, Fs_type, Bind)                                                           \
 229     (ME_DUMMY_0 (Fs_name, Fs_type) || (strcmp (Fs_type, "none") == 0 && !Bind))
 230 #else
 231 #define ME_DUMMY(Fs_name, Fs_type) (ME_DUMMY_0 (Fs_name, Fs_type) || strcmp (Fs_type, "none") == 0)
 232 #endif
 233 
 234 #ifdef __CYGWIN__
 235 #include <windows.h>
 236 #define ME_REMOTE me_remote
 237 /* All cygwin mount points include ':' or start with '//'; so it
 238    requires a native Windows call to determine remote disks.  */
 239 static int
 240 me_remote (char const *fs_name, char const *fs_type)
     /* [previous][next][first][last][top][bottom][index][help]  */
 241 {
 242     (void) fs_type;
 243 
 244     if (fs_name[0] && fs_name[1] == ':')
 245     {
 246         char drive[4];
 247         sprintf (drive, "%c:\\", fs_name[0]);
 248         switch (GetDriveType (drive))
 249         {
 250         case DRIVE_REMOVABLE:
 251         case DRIVE_FIXED:
 252         case DRIVE_CDROM:
 253         case DRIVE_RAMDISK:
 254             return 0;
 255         }
 256     }
 257     return 1;
 258 }
 259 #endif
 260 #ifndef ME_REMOTE
 261 /* A file system is 'remote' if its Fs_name contains a ':'
 262    or if (it is of type (smbfs or smb3 or cifs) and its Fs_name starts with '//')
 263    or if it is of any other of the listed types
 264    or Fs_name is equal to "-hosts" (used by autofs to mount remote fs).
 265    "VM" file systems like prl_fs or vboxsf are not considered remote here. */
 266 #define ME_REMOTE(Fs_name, Fs_type)                                                                \
 267     (strchr (Fs_name, ':') != NULL                                                                 \
 268      || ((Fs_name)[0] == '/' && (Fs_name)[1] == '/'                                                \
 269          && (strcmp (Fs_type, "smbfs") == 0 || strcmp (Fs_type, "smb3") == 0                       \
 270              || strcmp (Fs_type, "cifs") == 0))                                                    \
 271      || strcmp (Fs_type, "acfs") == 0 || strcmp (Fs_type, "afs") == 0                              \
 272      || strcmp (Fs_type, "coda") == 0 || strcmp (Fs_type, "auristorfs") == 0                       \
 273      || strcmp (Fs_type, "fhgfs") == 0 || strcmp (Fs_type, "gpfs") == 0                            \
 274      || strcmp (Fs_type, "ibrix") == 0 || strcmp (Fs_type, "ocfs2") == 0                           \
 275      || strcmp (Fs_type, "vxfs") == 0 || strcmp ("-hosts", Fs_name) == 0)
 276 #endif
 277 
 278 /* Many space usage primitives use all 1 bits to denote a value that is
 279    not applicable or unknown.  Propagate this information by returning
 280    a uintmax_t value that is all 1 bits if X is all 1 bits, even if X
 281    is unsigned and narrower than uintmax_t.  */
 282 #define PROPAGATE_ALL_ONES(x)                                                                      \
 283     ((sizeof (x) < sizeof (uintmax_t)                                                              \
 284       && (~(x) == (sizeof (x) < sizeof (int) ? -(1 << (sizeof (x) * CHAR_BIT)) : 0)))              \
 285          ? UINTMAX_MAX                                                                             \
 286          : (uintmax_t) (x))
 287 
 288 /* Extract the top bit of X as an uintmax_t value.  */
 289 #define EXTRACT_TOP_BIT(x) ((x) & ((uintmax_t) 1 << (sizeof (x) * CHAR_BIT - 1)))
 290 
 291 /* If a value is negative, many space usage primitives store it into an
 292    integer variable by assignment, even if the variable's type is unsigned.
 293    So, if a space usage variable X's top bit is set, convert X to the
 294    uintmax_t value V such that (- (uintmax_t) V) is the negative of
 295    the original value.  If X's top bit is clear, just yield X.
 296    Use PROPAGATE_TOP_BIT if the original value might be negative;
 297    otherwise, use PROPAGATE_ALL_ONES.  */
 298 #define PROPAGATE_TOP_BIT(x) ((x) | ~(EXTRACT_TOP_BIT (x) - 1))
 299 
 300 #ifdef STAT_STATVFS
 301 #if !(defined __linux__ && (defined __GLIBC__ || defined __UCLIBC__))
 302 /* The FRSIZE fallback is not required in this case.  */
 303 #undef STAT_STATFS2_FRSIZE
 304 #else
 305 #include <sys/utsname.h>
 306 #include <sys/statfs.h>
 307 #define STAT_STATFS2_BSIZE 1
 308 #endif
 309 #endif
 310 
 311 /*** file scope type declarations ****************************************************************/
 312 
 313 /* A mount table entry. */
 314 struct mount_entry
 315 {
 316     char *me_devname;                   /* Device node name, including "/dev/". */
 317     char *me_mountdir;                  // Mount point directory name.
 318     char *me_mntroot;                   /* Directory on filesystem of device used
 319                                            as root for the (bind) mount. */
 320     char *me_type;                      // "nfs", "4.2", etc.
 321     dev_t me_dev;                       // Device number of me_mountdir.
 322     unsigned int me_dummy : 1;          // Nonzero for dummy file systems.
 323     unsigned int me_remote : 1;         // Nonzero for remote filesystems.
 324     unsigned int me_type_malloced : 1;  // Nonzero if me_type was malloced.
 325 };
 326 
 327 struct fs_usage
 328 {
 329     uintmax_t fsu_blocksize;     // Size of a block.
 330     uintmax_t fsu_blocks;        // Total blocks.
 331     uintmax_t fsu_bfree;         // Free blocks available to superuser.
 332     uintmax_t fsu_bavail;        // Free blocks available to non-superuser.
 333     int fsu_bavail_top_bit_set;  // 1 if fsu_bavail represents a value < 0.
 334     uintmax_t fsu_files;         // Total file nodes.
 335     uintmax_t fsu_ffree;         // Free file nodes.
 336 };
 337 
 338 /*** forward declarations (file scope functions) *************************************************/
 339 
 340 /*** file scope variables ************************************************************************/
 341 
 342 #ifdef HAVE_INFOMOUNT_LIST
 343 static GSList *mc_mount_list = NULL;
 344 #endif
 345 
 346 /* --------------------------------------------------------------------------------------------- */
 347 /*** file scope functions ************************************************************************/
 348 /* --------------------------------------------------------------------------------------------- */
 349 
 350 #ifdef STAT_STATVFS
 351 /* Return true if statvfs works.  This is false for statvfs on systems
 352    with GNU libc on Linux kernels before 2.6.36, which stats all
 353    preceding entries in /proc/mounts; that makes df hang if even one
 354    of the corresponding file systems is hard-mounted but not available.  */
 355 static int
 356 statvfs_works (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 357 {
 358 #if !(defined __linux__ && (defined __GLIBC__ || defined __UCLIBC__))
 359     return 1;
 360 #else
 361     static int statvfs_works_cache = -1;
 362     struct utsname name;
 363 
 364     if (statvfs_works_cache < 0)
 365         statvfs_works_cache = (uname (&name) == 0 && 0 <= str_verscmp (name.release, "2.6.36"));
 366     return statvfs_works_cache;
 367 #endif
 368 }
 369 #endif
 370 
 371 /* --------------------------------------------------------------------------------------------- */
 372 
 373 #ifdef HAVE_INFOMOUNT_LIST
 374 static void
 375 free_mount_entry (struct mount_entry *me)
     /* [previous][next][first][last][top][bottom][index][help]  */
 376 {
 377     if (me == NULL)
 378         return;
 379     g_free (me->me_devname);
 380     g_free (me->me_mountdir);
 381     g_free (me->me_mntroot);
 382     if (me->me_type_malloced)
 383         g_free (me->me_type);
 384     g_free (me);
 385 }
 386 
 387 /* --------------------------------------------------------------------------------------------- */
 388 
 389 #ifdef MOUNTED_GETMNTINFO  // Mac OS X, FreeBSD, OpenBSD, also (obsolete) 4.4BSD
 390 
 391 #ifndef HAVE_STRUCT_STATFS_F_FSTYPENAME
 392 static const char *
 393 fstype_to_string (short int t)
     /* [previous][next][first][last][top][bottom][index][help]  */
 394 {
 395     switch (t)
 396     {
 397 #ifdef MOUNT_PC
 398     case MOUNT_PC:
 399         return "pc";
 400 #endif
 401 #ifdef MOUNT_MFS
 402     case MOUNT_MFS:
 403         return "mfs";
 404 #endif
 405 #ifdef MOUNT_LO
 406     case MOUNT_LO:
 407         return "lo";
 408 #endif
 409 #ifdef MOUNT_TFS
 410     case MOUNT_TFS:
 411         return "tfs";
 412 #endif
 413 #ifdef MOUNT_TMP
 414     case MOUNT_TMP:
 415         return "tmp";
 416 #endif
 417 #ifdef MOUNT_UFS
 418     case MOUNT_UFS:
 419         return "ufs";
 420 #endif
 421 #ifdef MOUNT_NFS
 422     case MOUNT_NFS:
 423         return "nfs";
 424 #endif
 425 #ifdef MOUNT_MSDOS
 426     case MOUNT_MSDOS:
 427         return "msdos";
 428 #endif
 429 #ifdef MOUNT_LFS
 430     case MOUNT_LFS:
 431         return "lfs";
 432 #endif
 433 #ifdef MOUNT_LOFS
 434     case MOUNT_LOFS:
 435         return "lofs";
 436 #endif
 437 #ifdef MOUNT_FDESC
 438     case MOUNT_FDESC:
 439         return "fdesc";
 440 #endif
 441 #ifdef MOUNT_PORTAL
 442     case MOUNT_PORTAL:
 443         return "portal";
 444 #endif
 445 #ifdef MOUNT_NULL
 446     case MOUNT_NULL:
 447         return "null";
 448 #endif
 449 #ifdef MOUNT_UMAP
 450     case MOUNT_UMAP:
 451         return "umap";
 452 #endif
 453 #ifdef MOUNT_KERNFS
 454     case MOUNT_KERNFS:
 455         return "kernfs";
 456 #endif
 457 #ifdef MOUNT_PROCFS
 458     case MOUNT_PROCFS:
 459         return "procfs";
 460 #endif
 461 #ifdef MOUNT_AFS
 462     case MOUNT_AFS:
 463         return "afs";
 464 #endif
 465 #ifdef MOUNT_CD9660
 466     case MOUNT_CD9660:
 467         return "cd9660";
 468 #endif
 469 #ifdef MOUNT_UNION
 470     case MOUNT_UNION:
 471         return "union";
 472 #endif
 473 #ifdef MOUNT_DEVFS
 474     case MOUNT_DEVFS:
 475         return "devfs";
 476 #endif
 477 #ifdef MOUNT_EXT2FS
 478     case MOUNT_EXT2FS:
 479         return "ext2fs";
 480 #endif
 481     default:
 482         return "?";
 483     }
 484 }
 485 #endif
 486 
 487 /* --------------------------------------------------------------------------------------------- */
 488 
 489 static const char *
 490 fsp_to_string (const struct statfs *fsp)
     /* [previous][next][first][last][top][bottom][index][help]  */
 491 {
 492 #ifdef HAVE_STRUCT_STATFS_F_FSTYPENAME
 493     return fsp->f_fstypename;
 494 #else
 495     return fstype_to_string (fsp->f_type);
 496 #endif
 497 }
 498 #endif
 499 
 500 /* --------------------------------------------------------------------------------------------- */
 501 
 502 #ifdef MOUNTED_VMOUNT  // AIX
 503 static const char *
 504 fstype_to_string (int t)
     /* [previous][next][first][last][top][bottom][index][help]  */
 505 {
 506     struct vfs_ent *e;
 507 
 508     e = getvfsbytype (t);
 509     if (!e || !e->vfsent_name)
 510         return "none";
 511     else
 512         return e->vfsent_name;
 513 }
 514 #endif
 515 
 516 /* --------------------------------------------------------------------------------------------- */
 517 
 518 #if defined MOUNTED_GETMNTENT1 || defined MOUNTED_GETMNTENT2
 519 
 520 /* Return the device number from MOUNT_OPTIONS, if possible.
 521    Otherwise return (dev_t) -1.  */
 522 
 523 /* --------------------------------------------------------------------------------------------- */
 524 
 525 static dev_t
 526 dev_from_mount_options (char const *mount_options)
     /* [previous][next][first][last][top][bottom][index][help]  */
 527 {
 528     /* GNU/Linux allows file system implementations to define their own
 529        meaning for "dev=" mount options, so don't trust the meaning
 530        here.  */
 531 #ifndef __linux__
 532     static char const dev_pattern[] = ",dev=";
 533     char const *devopt = strstr (mount_options, dev_pattern);
 534 
 535     if (devopt)
 536     {
 537         char const *optval = devopt + sizeof (dev_pattern) - 1;
 538 
 539         if (g_ascii_isxdigit (*optval))
 540         {
 541             char *optvalend;
 542             unsigned long int dev;
 543 
 544             errno = 0;
 545             dev = strtoul (optval, &optvalend, 16);
 546             if (optval != optvalend && (*optvalend == '\0' || *optvalend == ',')
 547                 && !(dev == ULONG_MAX && errno == ERANGE) && dev == (dev_t) dev)
 548                 return dev;
 549         }
 550     }
 551 #endif
 552 
 553     (void) mount_options;
 554     return -1;
 555 }
 556 
 557 #endif
 558 
 559 /* --------------------------------------------------------------------------------------------- */
 560 
 561 #if defined MOUNTED_GETMNTENT1 && (defined __linux__ || defined __ANDROID__)  // GNU/Linux, Android
 562 
 563 /* Unescape the paths in mount tables.
 564    STR is updated in place.  */
 565 static void
 566 unescape_tab (char *str)
     /* [previous][next][first][last][top][bottom][index][help]  */
 567 {
 568     size_t i, j = 0;
 569     size_t len;
 570 
 571     len = strlen (str) + 1;
 572 
 573     for (i = 0; i < len; i++)
 574     {
 575         if (str[i] == '\\' && (i + 4 < len) && str[i + 1] >= '0' && str[i + 1] <= '3'
 576             && str[i + 2] >= '0' && str[i + 2] <= '7' && str[i + 3] >= '0' && str[i + 3] <= '7')
 577         {
 578             str[j++] = (str[i + 1] - '0') * 64 + (str[i + 2] - '0') * 8 + (str[i + 3] - '0');
 579             i += 3;
 580         }
 581         else
 582             str[j++] = str[i];
 583     }
 584 }
 585 #endif
 586 
 587 /* --------------------------------------------------------------------------------------------- */
 588 
 589 /* Return a list of the currently mounted file systems, or NULL on error.
 590    Add each entry to the tail of the list so that they stay in order. */
 591 
 592 static GSList *
 593 read_file_system_list (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 594 {
 595     GSList *mount_list = NULL;
 596     struct mount_entry *me;
 597 
 598 #ifdef MOUNTED_GETMNTENT1 /* glibc, HP-UX, Cygwin, Android,                                        \
 599                              also (obsolete) 4.3BSD, SunOS */
 600     {
 601         FILE *fp;
 602 
 603 #if defined __linux__ || defined __ANDROID__
 604         /* Try parsing mountinfo first, as that make device IDs available.
 605            Note we could use libmount routines to simplify this parsing a little
 606            (and that code is in previous versions of this function), however
 607            libmount depends on libselinux which pulls in many dependencies.  */
 608         char const *mountinfo = "/proc/self/mountinfo";
 609 
 610         fp = fopen (mountinfo, "r");
 611         if (fp != NULL)
 612         {
 613             char *line = NULL;
 614             size_t buf_size = 0;
 615 
 616             while (getline (&line, &buf_size, fp) != -1)
 617             {
 618                 unsigned int devmaj, devmin;
 619                 int target_s, target_e, type_s, type_e;
 620                 int source_s, source_e, mntroot_s, mntroot_e;
 621                 char test;
 622                 char *dash;
 623                 int rc;
 624 
 625                 rc = sscanf (line,
 626                              "%*u "      // id - discarded
 627                              "%*u "      // parent - discarded
 628                              "%u:%u "    // dev major:minor
 629                              "%n%*s%n "  // mountroot
 630                              "%n%*s%n"   // target, start and end
 631                              "%c",       // more data...
 632                              &devmaj, &devmin, &mntroot_s, &mntroot_e, &target_s, &target_e, &test);
 633 
 634                 if (rc != 3 && rc != 7)  // 7 if %n included in count.
 635                     continue;
 636 
 637                 // skip optional fields, terminated by " - "
 638                 dash = strstr (line + target_e, " - ");
 639                 if (dash == NULL)
 640                     continue;
 641 
 642                 rc = sscanf (dash,
 643                              " - "       //
 644                              "%n%*s%n "  // FS type, start and end
 645                              "%n%*s%n "  // source, start and end
 646                              "%c",       // more data...
 647                              &type_s, &type_e, &source_s, &source_e, &test);
 648                 if (rc != 1 && rc != 5)  // 5 if %n included in count.
 649                     continue;
 650 
 651                 // manipulate the sub-strings in place.
 652                 line[mntroot_e] = '\0';
 653                 line[target_e] = '\0';
 654                 dash[type_e] = '\0';
 655                 dash[source_e] = '\0';
 656                 unescape_tab (dash + source_s);
 657                 unescape_tab (line + target_s);
 658                 unescape_tab (line + mntroot_s);
 659 
 660                 me = g_malloc (sizeof *me);
 661 
 662                 me->me_devname = g_strdup (dash + source_s);
 663                 me->me_mountdir = g_strdup (line + target_s);
 664                 me->me_mntroot = g_strdup (line + mntroot_s);
 665                 me->me_type = g_strdup (dash + type_s);
 666                 me->me_type_malloced = 1;
 667                 me->me_dev = makedev (devmaj, devmin);
 668                 /* we pass "false" for the "Bind" option as that's only
 669                    significant when the Fs_type is "none" which will not be
 670                    the case when parsing "/proc/self/mountinfo", and only
 671                    applies for static /etc/mtab files.  */
 672                 me->me_dummy = ME_DUMMY (me->me_devname, me->me_type, FALSE);
 673                 me->me_remote = ME_REMOTE (me->me_devname, me->me_type);
 674 
 675                 mount_list = g_slist_prepend (mount_list, me);
 676             }
 677 
 678             free (line);
 679 
 680             if (ferror (fp) != 0)
 681             {
 682                 int saved_errno = errno;
 683 
 684                 fclose (fp);
 685                 errno = saved_errno;
 686                 goto free_then_fail;
 687             }
 688 
 689             if (fclose (fp) == EOF)
 690                 goto free_then_fail;
 691         }
 692         else  // fallback to /proc/self/mounts (/etc/mtab)
 693 #endif
 694         {
 695             struct mntent *mnt;
 696             const char *table = MOUNTED;
 697 
 698             fp = setmntent (table, "r");
 699             if (fp == NULL)
 700                 return NULL;
 701 
 702             while ((mnt = getmntent (fp)) != NULL)
 703             {
 704                 gboolean bind;
 705 
 706                 bind = hasmntopt (mnt, "bind") != NULL;
 707 
 708                 me = g_malloc (sizeof (*me));
 709                 me->me_devname = g_strdup (mnt->mnt_fsname);
 710                 me->me_mountdir = g_strdup (mnt->mnt_dir);
 711                 me->me_mntroot = NULL;
 712                 me->me_type = g_strdup (mnt->mnt_type);
 713                 me->me_type_malloced = 1;
 714                 me->me_dummy = ME_DUMMY (me->me_devname, me->me_type, bind);
 715                 me->me_remote = ME_REMOTE (me->me_devname, me->me_type);
 716                 me->me_dev = dev_from_mount_options (mnt->mnt_opts);
 717 
 718                 mount_list = g_slist_prepend (mount_list, me);
 719             }
 720 
 721             if (endmntent (fp) == 0)
 722                 goto free_then_fail;
 723         }
 724     }
 725 #endif
 726 
 727 #ifdef MOUNTED_GETMNTINFO  // Mac OS X, FreeBSD, OpenBSD, also (obsolete) 4.4BSD
 728     {
 729         struct statfs *fsp;
 730         int entries;
 731 
 732         entries = getmntinfo (&fsp, MNT_NOWAIT);
 733         if (entries < 0)
 734             return NULL;
 735         for (; entries-- > 0; fsp++)
 736         {
 737             const char *fs_type = fsp_to_string (fsp);
 738 
 739             me = g_malloc (sizeof (*me));
 740             me->me_devname = g_strdup (fsp->f_mntfromname);
 741             me->me_mountdir = g_strdup (fsp->f_mntonname);
 742             me->me_mntroot = NULL;
 743             me->me_type = g_strdup (fs_type);
 744             me->me_type_malloced = 1;
 745             me->me_dummy = ME_DUMMY (me->me_devname, me->me_type);
 746             me->me_remote = ME_REMOTE (me->me_devname, me->me_type);
 747             me->me_dev = (dev_t) (-1);  // Magic; means not known yet.
 748 
 749             mount_list = g_slist_prepend (mount_list, me);
 750         }
 751     }
 752 #endif
 753 
 754 #ifdef MOUNTED_GETMNTINFO2  // NetBSD, Minix
 755     {
 756         struct statvfs *fsp;
 757         int entries;
 758 
 759         entries = getmntinfo (&fsp, MNT_NOWAIT);
 760         if (entries < 0)
 761             return NULL;
 762         for (; entries-- > 0; fsp++)
 763         {
 764             me = g_malloc (sizeof (*me));
 765             me->me_devname = g_strdup (fsp->f_mntfromname);
 766             me->me_mountdir = g_strdup (fsp->f_mntonname);
 767             me->me_mntroot = NULL;
 768             me->me_type = g_strdup (fsp->f_fstypename);
 769             me->me_type_malloced = 1;
 770             me->me_dummy = ME_DUMMY (me->me_devname, me->me_type);
 771             me->me_remote = ME_REMOTE (me->me_devname, me->me_type);
 772             me->me_dev = (dev_t) (-1);  // Magic; means not known yet.
 773 
 774             mount_list = g_slist_prepend (mount_list, me);
 775         }
 776     }
 777 #endif
 778 
 779 #if defined MOUNTED_FS_STAT_DEV  // Haiku, also (obsolete) BeOS
 780     {
 781         /* The next_dev() and fs_stat_dev() system calls give the list of
 782            all file systems, including the information returned by statvfs()
 783            (fs type, total blocks, free blocks etc.), but without the mount
 784            point. But on BeOS all file systems except / are mounted in the
 785            rootfs, directly under /.
 786            The directory name of the mount point is often, but not always,
 787            identical to the volume name of the device.
 788            We therefore get the list of subdirectories of /, and the list
 789            of all file systems, and match the two lists.  */
 790 
 791         DIR *dirp;
 792         struct rootdir_entry
 793         {
 794             char *name;
 795             dev_t dev;
 796             ino_t ino;
 797             struct rootdir_entry *next;
 798         };
 799         struct rootdir_entry *rootdir_list;
 800         struct rootdir_entry **rootdir_tail;
 801         int32 pos;
 802         dev_t dev;
 803         fs_info fi;
 804 
 805         // All volumes are mounted in the rootfs, directly under /
 806         rootdir_list = NULL;
 807         rootdir_tail = &rootdir_list;
 808         dirp = opendir (PATH_SEP_STR);
 809         if (dirp)
 810         {
 811             struct dirent *d;
 812 
 813             while ((d = readdir (dirp)) != NULL)
 814             {
 815                 char *name;
 816                 struct stat statbuf;
 817 
 818                 if (DIR_IS_DOT (d->d_name))
 819                     continue;
 820 
 821                 if (DIR_IS_DOTDOT (d->d_name))
 822                     name = g_strdup (PATH_SEP_STR);
 823                 else
 824                     name = g_strconcat (PATH_SEP_STR, d->d_name, (char *) NULL);
 825 
 826                 if (lstat (name, &statbuf) >= 0 && S_ISDIR (statbuf.st_mode))
 827                 {
 828                     struct rootdir_entry *re = g_malloc (sizeof (*re));
 829                     re->name = name;
 830                     re->dev = statbuf.st_dev;
 831                     re->ino = statbuf.st_ino;
 832 
 833                     // Add to the linked list.
 834                     *rootdir_tail = re;
 835                     rootdir_tail = &re->next;
 836                 }
 837                 else
 838                     g_free (name);
 839             }
 840             closedir (dirp);
 841         }
 842         *rootdir_tail = NULL;
 843 
 844         for (pos = 0; (dev = next_dev (&pos)) >= 0;)
 845             if (fs_stat_dev (dev, &fi) >= 0)
 846             {
 847                 // Note: fi.dev == dev.
 848                 struct rootdir_entry *re;
 849 
 850                 for (re = rootdir_list; re; re = re->next)
 851                     if (re->dev == fi.dev && re->ino == fi.root)
 852                         break;
 853 
 854                 me = g_malloc (sizeof (*me));
 855                 me->me_devname =
 856                     g_strdup (fi.device_name[0] != '\0' ? fi.device_name : fi.fsh_name);
 857                 me->me_mountdir = g_strdup (re != NULL ? re->name : fi.fsh_name);
 858                 me->me_mntroot = NULL;
 859                 me->me_type = g_strdup (fi.fsh_name);
 860                 me->me_type_malloced = 1;
 861                 me->me_dev = fi.dev;
 862                 me->me_dummy = 0;
 863                 me->me_remote = (fi.flags & B_FS_IS_SHARED) != 0;
 864 
 865                 mount_list = g_slist_prepend (mount_list, me);
 866             }
 867 
 868         while (rootdir_list != NULL)
 869         {
 870             struct rootdir_entry *re = rootdir_list;
 871 
 872             rootdir_list = re->next;
 873             g_free (re->name);
 874             g_free (re);
 875         }
 876     }
 877 #endif
 878 
 879 #ifdef MOUNTED_GETFSSTAT  //  (obsolete) Apple Darwin 1.3
 880     {
 881         int numsys, counter;
 882         size_t bufsize;
 883         struct statfs *stats;
 884 
 885         numsys = getfsstat (NULL, 0L, MNT_NOWAIT);
 886         if (numsys < 0)
 887             return NULL;
 888         if (SIZE_MAX / sizeof (*stats) <= numsys)
 889         {
 890             fprintf (stderr, "%s\n", _ ("Memory exhausted!"));
 891             exit (EXIT_FAILURE);
 892         }
 893 
 894         bufsize = (1 + numsys) * sizeof (*stats);
 895         stats = g_malloc (bufsize);
 896         numsys = getfsstat (stats, bufsize, MNT_NOWAIT);
 897 
 898         if (numsys < 0)
 899         {
 900             g_free (stats);
 901             return NULL;
 902         }
 903 
 904         for (counter = 0; counter < numsys; counter++)
 905         {
 906             me = g_malloc (sizeof (*me));
 907             me->me_devname = g_strdup (stats[counter].f_mntfromname);
 908             me->me_mountdir = g_strdup (stats[counter].f_mntonname);
 909             me->me_mntroot = NULL;
 910             me->me_type = g_strdup (FS_TYPE (stats[counter]));
 911             me->me_type_malloced = 1;
 912             me->me_dummy = ME_DUMMY (me->me_devname, me->me_type);
 913             me->me_remote = ME_REMOTE (me->me_devname, me->me_type);
 914             me->me_dev = (dev_t) (-1);  // Magic; means not known yet.
 915 
 916             mount_list = g_slist_prepend (mount_list, me);
 917         }
 918 
 919         g_free (stats);
 920     }
 921 #endif
 922 
 923 #if defined MOUNTED_FREAD_FSTYP  // (obsolete) SVR3
 924     {
 925         struct mnttab mnt;
 926         char *table = "/etc/mnttab";
 927         FILE *fp;
 928 
 929         fp = fopen (table, "r");
 930         if (fp == NULL)
 931             return NULL;
 932 
 933         while (fread (&mnt, sizeof (mnt), 1, fp) > 0)
 934         {
 935             me = g_malloc (sizeof (*me));
 936             me->me_devname = g_strdup (mnt.mt_dev);
 937             me->me_mountdir = g_strdup (mnt.mt_filsys);
 938             me->me_mntroot = NULL;
 939             me->me_dev = (dev_t) (-1);  // Magic; means not known yet.
 940             me->me_type = "";
 941             me->me_type_malloced = 0;
 942             {
 943                 struct statfs fsd;
 944                 char typebuf[FSTYPSZ];
 945 
 946                 if (statfs (me->me_mountdir, &fsd, sizeof (fsd), 0) != -1
 947                     && sysfs (GETFSTYP, fsd.f_fstyp, typebuf) != -1)
 948                 {
 949                     me->me_type = g_strdup (typebuf);
 950                     me->me_type_malloced = 1;
 951                 }
 952             }
 953             me->me_dummy = ME_DUMMY (me->me_devname, me->me_type);
 954             me->me_remote = ME_REMOTE (me->me_devname, me->me_type);
 955 
 956             mount_list = g_slist_prepend (mount_list, me);
 957         }
 958 
 959         if (ferror (fp))
 960         {
 961             // The last fread() call must have failed.
 962             int saved_errno = errno;
 963 
 964             fclose (fp);
 965             errno = saved_errno;
 966             goto free_then_fail;
 967         }
 968 
 969         if (fclose (fp) == EOF)
 970             goto free_then_fail;
 971     }
 972 #endif
 973 
 974 #ifdef MOUNTED_GETEXTMNTENT  // Solaris >= 8
 975     {
 976         struct extmnttab mnt;
 977         const char *table = MNTTAB;
 978         FILE *fp;
 979         int ret;
 980 
 981         // No locking is needed, because the contents of /etc/mnttab is generated by the kernel
 982 
 983         errno = 0;
 984         fp = fopen (table, "r");
 985         if (fp == NULL)
 986             ret = errno;
 987         else
 988         {
 989             while ((ret = getextmntent (fp, &mnt, 1)) == 0)
 990             {
 991                 me = g_malloc (sizeof *me);
 992                 me->me_devname = g_strdup (mnt.mnt_special);
 993                 me->me_mountdir = g_strdup (mnt.mnt_mountp);
 994                 me->me_mntroot = NULL;
 995                 me->me_type = g_strdup (mnt.mnt_fstype);
 996                 me->me_type_malloced = 1;
 997                 /* The cast from 'struct extmnttab *' to 'struct mnttab *' is OK
 998                    because 'struct extmnttab' extends 'struct mnttab'.  */
 999                 me->me_dummy = MNT_IGNORE ((struct mnttab *) &mnt) != 0;
1000                 me->me_remote = ME_REMOTE (me->me_devname, me->me_type);
1001                 me->me_dev = makedev (mnt.mnt_major, mnt.mnt_minor);
1002 
1003                 mount_list = g_slist_prepend (mount_list, me);
1004             }
1005 
1006             ret = fclose (fp) == EOF ? errno : 0 < ret ? 0 : -1;
1007             // Here ret = -1 means success, ret >= 0 means failure.
1008         }
1009 
1010         if (ret >= 0)
1011         {
1012             errno = ret;
1013             goto free_then_fail;
1014         }
1015     }
1016 #endif
1017 
1018 #ifdef MOUNTED_GETMNTENT2  // Solaris < 8, also (obsolete) SVR4
1019     {
1020         struct mnttab mnt;
1021         const char *table = MNTTAB;
1022         FILE *fp;
1023         int ret;
1024         int lockfd = -1;
1025 
1026 #if defined F_RDLCK && defined F_SETLKW
1027         /* MNTTAB_LOCK is a macro name of our own invention; it's not present in
1028            e.g. Solaris 2.6.  If the SVR4 folks ever define a macro
1029            for this file name, we should use their macro name instead.
1030            (Why not just lock MNTTAB directly?  We don't know.)  */
1031 #ifndef MNTTAB_LOCK
1032 #define MNTTAB_LOCK "/etc/.mnttab.lock"
1033 #endif
1034         lockfd = open (MNTTAB_LOCK, O_RDONLY);
1035         if (lockfd >= 0)
1036         {
1037             struct flock flock;
1038 
1039             flock.l_type = F_RDLCK;
1040             flock.l_whence = SEEK_SET;
1041             flock.l_start = 0;
1042             flock.l_len = 0;
1043             while (fcntl (lockfd, F_SETLKW, &flock) == -1)
1044                 if (errno != EINTR)
1045                 {
1046                     int saved_errno = errno;
1047                     close (lockfd);
1048                     errno = saved_errno;
1049                     return NULL;
1050                 }
1051         }
1052         else if (errno != ENOENT)
1053             return NULL;
1054 #endif
1055 
1056         errno = 0;
1057         fp = fopen (table, "r");
1058         if (fp == NULL)
1059             ret = errno;
1060         else
1061         {
1062             while ((ret = getmntent (fp, &mnt)) == 0)
1063             {
1064                 me = g_malloc (sizeof (*me));
1065                 me->me_devname = g_strdup (mnt.mnt_special);
1066                 me->me_mountdir = g_strdup (mnt.mnt_mountp);
1067                 me->me_mntroot = NULL;
1068                 me->me_type = g_strdup (mnt.mnt_fstype);
1069                 me->me_type_malloced = 1;
1070                 me->me_dummy = MNT_IGNORE (&mnt) != 0;
1071                 me->me_remote = ME_REMOTE (me->me_devname, me->me_type);
1072                 me->me_dev = dev_from_mount_options (mnt.mnt_mntopts);
1073 
1074                 mount_list = g_slist_prepend (mount_list, me);
1075             }
1076 
1077             ret = fclose (fp) == EOF ? errno : 0 < ret ? 0 : -1;
1078             // Here ret = -1 means success, ret >= 0 means failure.
1079         }
1080 
1081         if (lockfd >= 0 && close (lockfd) != 0)
1082             ret = errno;
1083 
1084         if (ret >= 0)
1085         {
1086             errno = ret;
1087             goto free_then_fail;
1088         }
1089     }
1090 #endif
1091 
1092 #ifdef MOUNTED_VMOUNT  // AIX
1093     {
1094         int bufsize;
1095         void *entries;
1096         char *thisent;
1097         struct vmount *vmp;
1098         int n_entries;
1099         int i;
1100 
1101         // Ask how many bytes to allocate for the mounted file system info.
1102         if (mntctl (MCTL_QUERY, sizeof (bufsize), (char *) &bufsize) != 0)
1103             return NULL;
1104         entries = g_malloc (bufsize);
1105 
1106         // Get the list of mounted file systems.
1107         n_entries = mntctl (MCTL_QUERY, bufsize, entries);
1108         if (n_entries < 0)
1109         {
1110             int saved_errno = errno;
1111 
1112             g_free (entries);
1113             errno = saved_errno;
1114             return NULL;
1115         }
1116 
1117         for (i = 0, thisent = entries; i < n_entries; i++, thisent += vmp->vmt_length)
1118         {
1119             char *options, *ignore;
1120 
1121             vmp = (struct vmount *) thisent;
1122             me = g_malloc (sizeof (*me));
1123             if (vmp->vmt_flags & MNT_REMOTE)
1124             {
1125                 char *host, *dir;
1126 
1127                 me->me_remote = 1;
1128                 // Prepend the remote dirname.
1129                 host = thisent + vmp->vmt_data[VMT_HOSTNAME].vmt_off;
1130                 dir = thisent + vmp->vmt_data[VMT_OBJECT].vmt_off;
1131                 me->me_devname = g_strconcat (host, ":", dir, (char *) NULL);
1132             }
1133             else
1134             {
1135                 me->me_remote = 0;
1136                 me->me_devname = g_strdup (thisent + vmp->vmt_data[VMT_OBJECT].vmt_off);
1137             }
1138             me->me_mountdir = g_strdup (thisent + vmp->vmt_data[VMT_STUB].vmt_off);
1139             me->me_mntroot = NULL;
1140             me->me_type = g_strdup (fstype_to_string (vmp->vmt_gfstype));
1141             me->me_type_malloced = 1;
1142             options = thisent + vmp->vmt_data[VMT_ARGS].vmt_off;
1143             ignore = strstr (options, "ignore");
1144             me->me_dummy = (ignore && (ignore == options || ignore[-1] == ',')
1145                             && (ignore[sizeof ("ignore") - 1] == ','
1146                                 || ignore[sizeof ("ignore") - 1] == '\0'));
1147             me->me_dev = (dev_t) (-1);  // vmt_fsid might be the info we want.
1148 
1149             mount_list = g_slist_prepend (mount_list, me);
1150         }
1151         g_free (entries);
1152     }
1153 #endif
1154 
1155 #ifdef MOUNTED_INTERIX_STATVFS  // Interix
1156     {
1157         DIR *dirp = opendir ("/dev/fs");
1158         char node[9 + NAME_MAX];
1159 
1160         if (!dirp)
1161             goto free_then_fail;
1162 
1163         while (1)
1164         {
1165             struct statvfs dev;
1166             struct dirent entry;
1167             struct dirent *result;
1168 
1169             if (readdir_r (dirp, &entry, &result) || result == NULL)
1170                 break;
1171 
1172             strcpy (node, "/dev/fs/");
1173             strcat (node, entry.d_name);
1174 
1175             if (statvfs (node, &dev) == 0)
1176             {
1177                 me = g_malloc (sizeof *me);
1178                 me->me_devname = g_strdup (dev.f_mntfromname);
1179                 me->me_mountdir = g_strdup (dev.f_mntonname);
1180                 me->me_mntroot = NULL;
1181                 me->me_type = g_strdup (dev.f_fstypename);
1182                 me->me_type_malloced = 1;
1183                 me->me_dummy = ME_DUMMY (me->me_devname, me->me_type);
1184                 me->me_remote = ME_REMOTE (me->me_devname, me->me_type);
1185                 me->me_dev = (dev_t) (-1);  // Magic; means not known yet.
1186 
1187                 mount_list = g_slist_prepend (mount_list, me);
1188             }
1189         }
1190         closedir (dirp);
1191     }
1192 #endif
1193 
1194 #if defined _WIN32 && !defined __CYGWIN__  // native Windows
1195 // Don't assume that UNICODE is not defined.
1196 #undef GetDriveType
1197 #define GetDriveType GetDriveTypeA
1198 #undef GetVolumeInformation
1199 #define GetVolumeInformation GetVolumeInformationA
1200     {
1201         /* Windows has drive prefixes which are similar to mount points.
1202            GetLogicalDrives returns a bitmask where the i-th bit is set
1203            if ASCII 'A' + i is an available drive.  See:
1204            <https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getlogicaldrives>.
1205          */
1206         DWORD value = GetLogicalDrives ();
1207         unsigned int i;
1208 
1209         for (i = 0; i < 26; ++i)
1210         {
1211             if (value & (1U << i))
1212             {
1213                 char mountdir[4];
1214                 char fs_name[MAX_PATH + 1];
1215 
1216                 mountdir[0] = 'A' + i;
1217                 mountdir[1] = ':';
1218                 mountdir[2] = '\\';
1219                 mountdir[3] = '\0';
1220                 /* Test whether the drive actually exists, and
1221                    get the name of the file system.  See:
1222                    <https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getvolumeinformationa>.
1223                  */
1224                 if (GetVolumeInformation (mountdir, NULL, 0, NULL, NULL, NULL, fs_name,
1225                                           sizeof fs_name))
1226                 {
1227                     me = g_malloc (sizeof (*me));
1228                     me->me_mountdir = g_strdup (mountdir);
1229                     /* Check if drive is remote.  See:
1230                        <https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getdrivetypea>.
1231                      */
1232                     me->me_remote = GetDriveType (mountdir) == DRIVE_REMOTE;
1233                     /* Here we could use
1234                        QueryDosDeviceW -> returns something like '\Device\HarddiskVolume2'
1235                        GetVolumeNameForVolumeMountPointW -> return something like '\\?\Volume{...}'
1236                      */
1237                     me->me_devname = NULL;
1238                     {
1239                         /* Find the SUBST or NET USE mapping of the given drive.
1240                            <https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-querydosdevicew>
1241                            For testing of SUBST:   <https://ss64.com/nt/subst.html>
1242                            For testing of NET USE: <https://ss64.com/nt/net-use.html>  */
1243                         wchar_t drive[3];
1244                         wchar_t mapping[MAX_PATH + 1];
1245 
1246                         drive[0] = L'A' + i;
1247                         drive[1] = L':';
1248                         drive[2] = L'\0';
1249 
1250                         DWORD mapping_len = QueryDosDeviceW (
1251                             drive, mapping, sizeof (mapping) / sizeof (mapping[0]));
1252 
1253                         if (mapping_len > 4 && wcsncmp (mapping, L"\\??\\", 4) == 0)
1254                         {
1255                             // It's a SUBSTed drive.
1256                             char subst_dir[MAX_PATH + 1];
1257                             size_t subst_dir_len =
1258                                 wcstombs (subst_dir, mapping + 4, sizeof (subst_dir));
1259 
1260                             if (subst_dir_len > 0 && subst_dir_len <= MAX_PATH)
1261                                 me->me_mntroot = g_strdup (subst_dir);
1262                             else
1263                                 // mapping is too long or not convertible to the locale encoding.
1264                                 me->me_mntroot = NULL;
1265                         }
1266                         else if (mapping_len > 26
1267                                  && wcsncmp (mapping, L"\\Device\\LanmanRedirector\\;", 26) == 0)
1268                         {
1269                             wchar_t *next_backslash = wcschr (mapping + 26, L'\\');
1270 
1271                             if (next_backslash != NULL)
1272                             {
1273                                 *--next_backslash = L'\\';
1274 
1275                                 char share_dir[MAX_PATH + 1];
1276                                 size_t share_dir_len =
1277                                     wcstombs (share_dir, next_backslash, sizeof (share_dir));
1278 
1279                                 if (share_dir_len > 0 && share_dir_len <= MAX_PATH)
1280                                     me->me_mntroot = g_strdup (share_dir);
1281                                 else
1282                                     /* mapping is too long or not convertible to the locale
1283                                        encoding.  */
1284                                     me->me_mntroot = NULL;
1285                             }
1286                             else
1287                                 // mapping does not have the expected form.
1288                                 me->me_mntroot = NULL;
1289                         }
1290                         else
1291                             // It's neither a SUBSTed nor a NET USEd drive.
1292                             me->me_mntroot = NULL;
1293                     }
1294                     me->me_dev = (dev_t) -1;
1295                     me->me_dummy = 0;
1296                     me->me_type = g_strdup (fs_name);
1297                     me->me_type_malloced = 1;
1298                     // Add to the linked list.
1299                     *mtail = me;
1300                     mtail = &me->me_next;
1301                 }
1302             }
1303         }
1304     }
1305 
1306     {
1307         /* Windows also has true mount points, called "mounted folders".  See
1308            <https://learn.microsoft.com/en-us/windows/win32/fileio/volume-mount-points>
1309            For testing:
1310            <https://learn.microsoft.com/en-us/windows-server/storage/disk-management/assign-a-mount-point-folder-path-to-a-drive>
1311          */
1312         /* Enumerate the volumes.  See
1313            <https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-findfirstvolumew>
1314            <https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-findnextvolumew>
1315            <https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-findvolumeclose>
1316          */
1317         wchar_t vol_name[MAX_PATH + 1];
1318         HANDLE h = FindFirstVolumeW (vol_name, sizeof (vol_name) / sizeof (vol_name[0]));
1319 
1320         if (h != INVALID_HANDLE_VALUE)
1321         {
1322             do
1323             {
1324                 /* Look where the volume vol_name is mounted.
1325                    There are two APIs for doing this:
1326                      - FindFirstVolumeMountPointW, FindNextVolumeMountPointW,
1327                        FindVolumeMountPointClose.  This API always fails with
1328                        error code ERROR_ACCESS_DENIED.
1329                      - GetVolumePathNamesForVolumeNameW.  This API works but
1330                        may require a significantly larger buffer.
1331                        <https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getvolumepathnamesforvolumenamew>
1332                  */
1333                 wchar_t stack_buf[MAX_PATH + 2];
1334                 wchar_t *malloced_buf = NULL;
1335                 wchar_t *buf = stack_buf;
1336                 DWORD bufsize = sizeof (stack_buf) / sizeof (wchar_t);
1337                 BOOL success;
1338 
1339                 for (;;)
1340                 {
1341                     success = GetVolumePathNamesForVolumeNameW (vol_name, buf, bufsize, &bufsize);
1342                     if (!success && GetLastError () == ERROR_MORE_DATA)
1343                     {
1344                         g_free (malloced_buf);
1345                         malloced_buf = (wchar_t *) g_malloc (bufsize * sizeof (wchar_t));
1346                         buf = malloced_buf;
1347                     }
1348                     else
1349                         break;
1350                 }
1351                 if (success)
1352                 {
1353                     wchar_t *mount_dir = buf;
1354 
1355                     while (*mount_dir != L'\0')
1356                     {
1357                         // Drive mounts are already handled above.
1358                         if (!(mount_dir[0] >= L'A' && mount_dir[0] <= L'Z' && mount_dir[1] == L':'
1359                               && mount_dir[2] == L'\\' && mount_dir[3] == L'\0'))
1360                         {
1361                             char mountdir[MAX_PATH + 1];
1362                             size_t mountdir_len = wcstombs (mountdir, mount_dir, sizeof (mountdir));
1363 
1364                             if (mountdir_len > 0 && mountdir_len <= MAX_PATH)
1365                             {
1366                                 char fs_name[MAX_PATH + 1];
1367 
1368                                 /* Get the name of the file system.  See:
1369                                    <https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getvolumeinformationa>.
1370                                  */
1371                                 if (GetVolumeInformation (mountdir, NULL, 0, NULL, NULL, NULL,
1372                                                           fs_name, sizeof fs_name))
1373                                 {
1374                                     me = g_malloc (sizeof (*me));
1375                                     me->me_mountdir = xstrdup (mountdir);
1376                                     me->me_remote = false;
1377                                     // Here we could use vol_name, something like '\\?\Volume{...}'.
1378                                     me->me_devname = NULL;
1379                                     me->me_mntroot = NULL;
1380                                     me->me_dev = (dev_t) -1;
1381                                     me->me_dummy = 0;
1382                                     me->me_type = xstrdup (fs_name);
1383                                     me->me_type_malloced = 1;
1384 
1385                                     // Add to the linked list.
1386                                     *mtail = me;
1387                                     mtail = &me->me_next;
1388                                 }
1389                             }
1390                             else
1391                             {
1392                                 // mount_dir is too long or not convertible to the locale encoding.
1393                             }
1394                         }
1395                         mount_dir += wcslen (mount_dir) + 1;
1396                     }
1397                 }
1398                 g_free (malloced_buf);
1399             }
1400             while (FindNextVolumeW (h, vol_name, sizeof (vol_name) / sizeof (vol_name[0])));
1401             FindVolumeClose (h);
1402         }
1403     }
1404 #endif
1405 
1406 #ifdef MOUNTED_NOT_PORTED
1407 #error "Please port gnulib mountlist.c to your platform!"
1408 #endif
1409 
1410     return g_slist_reverse (mount_list);
1411 
1412 free_then_fail:
1413     MC_UNUSED;
1414     {
1415         int saved_errno = errno;
1416 
1417         g_slist_free_full (mount_list, (GDestroyNotify) free_mount_entry);
1418 
1419         errno = saved_errno;
1420         return NULL;
1421     }
1422 }
1423 #endif
1424 
1425 /* --------------------------------------------------------------------------------------------- */
1426 
1427 #ifdef HAVE_INFOMOUNT_QNX
1428 /**
1429  ** QNX has no [gs]etmnt*(), [gs]etfs*(), or /etc/mnttab, but can do
1430  ** this via the following code.
1431  ** Note that, as this is based on CWD, it only fills one mount_entry
1432  ** structure. See my_statfs() below for the "other side" of this hack.
1433  */
1434 
1435 static GSList *
1436 read_file_system_list (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
1437 {
1438     struct _disk_entry de;
1439     struct statfs fs;
1440     int i, fd;
1441     char *tp, dev[_POSIX_NAME_MAX], dir[_POSIX_PATH_MAX];
1442     struct mount_entry *me = NULL;
1443     static GSList *list = NULL;
1444 
1445     if (list != NULL)
1446     {
1447         me = (struct mount_entry *) list->data;
1448 
1449         g_free (me->me_devname);
1450         g_free (me->me_mountdir);
1451         g_free (me->me_mntroot);
1452         g_free (me->me_type);
1453     }
1454     else
1455     {
1456         me = (struct mount_entry *) g_malloc (sizeof (struct mount_entry));
1457         list = g_slist_prepend (list, me);
1458     }
1459 
1460     if (!getcwd (dir, _POSIX_PATH_MAX))
1461         return (NULL);
1462 
1463     fd = open (dir, O_RDONLY);
1464     if (fd == -1)
1465         return (NULL);
1466 
1467     i = disk_get_entry (fd, &de);
1468 
1469     close (fd);
1470 
1471     if (i == -1)
1472         return (NULL);
1473 
1474     switch (de.disk_type)
1475     {
1476     case _UNMOUNTED:
1477         tp = "unmounted";
1478         break;
1479     case _FLOPPY:
1480         tp = "Floppy";
1481         break;
1482     case _HARD:
1483         tp = "Hard";
1484         break;
1485     case _RAMDISK:
1486         tp = "Ram";
1487         break;
1488     case _REMOVABLE:
1489         tp = "Removable";
1490         break;
1491     case _TAPE:
1492         tp = "Tape";
1493         break;
1494     case _CDROM:
1495         tp = "CDROM";
1496         break;
1497     default:
1498         tp = "unknown";
1499     }
1500 
1501     if (fsys_get_mount_dev (dir, &dev) == -1)
1502         return (NULL);
1503 
1504     if (fsys_get_mount_pt (dev, &dir) == -1)
1505         return (NULL);
1506 
1507     me->me_devname = g_strdup (dev);
1508     me->me_mountdir = g_strdup (dir);
1509     me->me_mntroot = NULL;
1510     me->me_type = g_strdup (tp);
1511     me->me_dev = de.disk_type;
1512 
1513 #ifdef DEBUG
1514     fprintf (stderr,
1515              "disk_get_entry():\n\tdisk_type=%d (%s)\n\tdriver_name='%-*.*s'\n\tdisk_drv=%d\n",
1516              de.disk_type, tp, _DRIVER_NAME_LEN, _DRIVER_NAME_LEN, de.driver_name, de.disk_drv);
1517     fprintf (stderr, "fsys_get_mount_dev():\n\tdevice='%s'\n", dev);
1518     fprintf (stderr, "fsys_get_mount_pt():\n\tmount point='%s'\n", dir);
1519 #endif
1520 
1521     return (list);
1522 }
1523 #endif
1524 
1525 /* --------------------------------------------------------------------------------------------- */
1526 
1527 #ifdef HAVE_INFOMOUNT
1528 /* Fill in the fields of FSP with information about space usage for
1529    the file system on which FILE resides.
1530    DISK is the device on which FILE is mounted, for space-getting
1531    methods that need to know it.
1532    Return 0 if successful, -1 if not.  When returning -1, ensure that
1533    ERRNO is either a system error value, or zero if DISK is NULL
1534    on a system that requires a non-NULL value.  */
1535 static int
1536 get_fs_usage (char const *file, char const *disk, struct fs_usage *fsp)
     /* [previous][next][first][last][top][bottom][index][help]  */
1537 {
1538 #ifdef STAT_STATVFS  // POSIX, except pre-2.6.36 glibc/Linux
1539 
1540     if (statvfs_works ())
1541     {
1542         struct statvfs vfsd;
1543 
1544         if (statvfs (file, &vfsd) < 0)
1545             return -1;
1546 
1547         // f_frsize isn't guaranteed to be supported.
1548         fsp->fsu_blocksize = (vfsd.f_frsize ? PROPAGATE_ALL_ONES (vfsd.f_frsize)
1549                                             : PROPAGATE_ALL_ONES (vfsd.f_bsize));
1550 
1551         fsp->fsu_blocks = PROPAGATE_ALL_ONES (vfsd.f_blocks);
1552         fsp->fsu_bfree = PROPAGATE_ALL_ONES (vfsd.f_bfree);
1553         fsp->fsu_bavail = PROPAGATE_TOP_BIT (vfsd.f_bavail);
1554         fsp->fsu_bavail_top_bit_set = EXTRACT_TOP_BIT (vfsd.f_bavail) != 0;
1555         fsp->fsu_files = PROPAGATE_ALL_ONES (vfsd.f_files);
1556         fsp->fsu_ffree = PROPAGATE_ALL_ONES (vfsd.f_ffree);
1557     }
1558     else
1559 #endif
1560 
1561     {
1562 #if defined STAT_STATVFS64  // AIX
1563 
1564         struct statvfs64 fsd;
1565 
1566         if (statvfs64 (file, &fsd) < 0)
1567             return -1;
1568 
1569         // f_frsize isn't guaranteed to be supported.
1570         fsp->fsu_blocksize =
1571             fsd.f_frsize ? PROPAGATE_ALL_ONES (fsd.f_frsize) : PROPAGATE_ALL_ONES (fsd.f_bsize);
1572 
1573 #elif defined STAT_STATFS2_FRSIZE  // 2.6 < glibc/Linux < 2.6.36
1574 
1575         struct statfs fsd;
1576 
1577         if (statfs (file, &fsd) < 0)
1578             return -1;
1579 
1580         fsp->fsu_blocksize = PROPAGATE_ALL_ONES (fsd.f_frsize);
1581 
1582 #elif defined STAT_STATFS2_BSIZE /* glibc/Linux < 2.6, 4.3BSD, SunOS 4,                            \
1583                                     Mac OS X < 10.4, FreeBSD < 5.0,                                \
1584                                     NetBSD < 3.0, OpenBSD < 4.4 */
1585 
1586         struct statfs fsd;
1587 
1588         if (statfs (file, &fsd) < 0)
1589             return -1;
1590 
1591         fsp->fsu_blocksize = PROPAGATE_ALL_ONES (fsd.f_bsize);
1592 
1593 #ifdef STATFS_TRUNCATES_BLOCK_COUNTS
1594 
1595         /* In SunOS 4.1.2, 4.1.3, and 4.1.3_U1, the block counts in the
1596            struct statfs are truncated to 2GB.  These conditions detect that
1597            truncation, presumably without botching the 4.1.1 case, in which
1598            the values are not truncated.  The correct counts are stored in
1599            undocumented spare fields.  */
1600         if (fsd.f_blocks == 0x7fffffff / fsd.f_bsize && fsd.f_spare[0] > 0)
1601         {
1602             fsd.f_blocks = fsd.f_spare[0];
1603             fsd.f_bfree = fsd.f_spare[1];
1604             fsd.f_bavail = fsd.f_spare[2];
1605         }
1606 #endif
1607 
1608 #elif defined STAT_STATFS2_FSIZE  // 4.4BSD and older NetBSD
1609 
1610         struct statfs fsd;
1611 
1612         if (statfs (file, &fsd) < 0)
1613             return -1;
1614 
1615         fsp->fsu_blocksize = PROPAGATE_ALL_ONES (fsd.f_fsize);
1616 
1617 #elif defined STAT_STATFS4  // SVR3
1618 
1619         struct statfs fsd;
1620 
1621         if (statfs (file, &fsd, sizeof (fsd), 0) < 0)
1622             return -1;
1623 
1624         /* Empirically, the block counts on most SVR3 and SVR3-derived
1625            systems seem to always be in terms of 512-byte blocks,
1626            no matter what value f_bsize has.  */
1627         fsp->fsu_blocksize = 512;
1628 #endif
1629 
1630 #if (defined STAT_STATVFS64 || defined STAT_STATFS2_FRSIZE || defined STAT_STATFS2_BSIZE           \
1631      || defined STAT_STATFS2_FSIZE || defined STAT_STATFS4)
1632 
1633         fsp->fsu_blocks = PROPAGATE_ALL_ONES (fsd.f_blocks);
1634         fsp->fsu_bfree = PROPAGATE_ALL_ONES (fsd.f_bfree);
1635         fsp->fsu_bavail = PROPAGATE_TOP_BIT (fsd.f_bavail);
1636         fsp->fsu_bavail_top_bit_set = EXTRACT_TOP_BIT (fsd.f_bavail) != 0;
1637         fsp->fsu_files = PROPAGATE_ALL_ONES (fsd.f_files);
1638         fsp->fsu_ffree = PROPAGATE_ALL_ONES (fsd.f_ffree);
1639 
1640 #endif
1641     }
1642 
1643     (void) disk;  // avoid argument-unused warning
1644 
1645     return 0;
1646 }
1647 #endif
1648 
1649 /* --------------------------------------------------------------------------------------------- */
1650 /*** public functions ****************************************************************************/
1651 /* --------------------------------------------------------------------------------------------- */
1652 
1653 void
1654 free_my_statfs (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
1655 {
1656 #ifdef HAVE_INFOMOUNT_LIST
1657     g_clear_slist (&mc_mount_list, (GDestroyNotify) free_mount_entry);
1658 #endif
1659 }
1660 
1661 /* --------------------------------------------------------------------------------------------- */
1662 
1663 void
1664 init_my_statfs (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
1665 {
1666 #ifdef HAVE_INFOMOUNT_LIST
1667     free_my_statfs ();
1668     mc_mount_list = read_file_system_list ();
1669 #endif
1670 }
1671 
1672 /* --------------------------------------------------------------------------------------------- */
1673 
1674 void
1675 my_statfs (struct my_statfs *myfs_stats, const char *path)
     /* [previous][next][first][last][top][bottom][index][help]  */
1676 {
1677 #ifdef HAVE_INFOMOUNT_LIST
1678     size_t len = 0;
1679     struct mount_entry *entry = NULL;
1680     GSList *temp;
1681     struct fs_usage fs_use;
1682 
1683     for (temp = mc_mount_list; temp != NULL; temp = g_slist_next (temp))
1684     {
1685         struct mount_entry *me;
1686         size_t i;
1687 
1688         me = (struct mount_entry *) temp->data;
1689         i = strlen (me->me_mountdir);
1690         if (i > len && (strncmp (path, me->me_mountdir, i) == 0)
1691             && (entry == NULL || IS_PATH_SEP (path[i]) || path[i] == '\0'))
1692         {
1693             len = i;
1694             entry = me;
1695         }
1696     }
1697 
1698     if (entry != NULL)
1699     {
1700         memset (&fs_use, 0, sizeof (fs_use));
1701         get_fs_usage (entry->me_mountdir, NULL, &fs_use);
1702 
1703         myfs_stats->type = entry->me_dev;
1704         myfs_stats->typename = entry->me_type;
1705         myfs_stats->mpoint = entry->me_mountdir;
1706         myfs_stats->mroot = entry->me_mntroot;
1707         myfs_stats->device = entry->me_devname;
1708         myfs_stats->avail =
1709             ((uintmax_t) (getuid () ? fs_use.fsu_bavail : fs_use.fsu_bfree) * fs_use.fsu_blocksize)
1710             >> 10;
1711         myfs_stats->total = ((uintmax_t) fs_use.fsu_blocks * fs_use.fsu_blocksize) >> 10;
1712         myfs_stats->nfree = (uintmax_t) fs_use.fsu_ffree;
1713         myfs_stats->nodes = (uintmax_t) fs_use.fsu_files;
1714     }
1715     else
1716 #endif
1717 
1718 #ifdef HAVE_INFOMOUNT_QNX
1719         /*
1720          ** This is the "other side" of the hack to read_file_system_list() above.
1721          ** It's not the most efficient approach, but consumes less memory. It
1722          ** also accommodates QNX's ability to mount filesystems on the fly.
1723          */
1724         struct mount_entry *entry;
1725     struct fs_usage fs_use;
1726 
1727     entry = read_file_system_list ();
1728     if (entry != NULL)
1729     {
1730         get_fs_usage (entry->me_mountdir, NULL, &fs_use);
1731 
1732         myfs_stats->type = entry->me_dev;
1733         myfs_stats->typename = entry->me_type;
1734         myfs_stats->mpoint = entry->me_mountdir;
1735         myfs_stats->mroot = entry->me_mntroot;
1736         myfs_stats->device = entry->me_devname;
1737 
1738         myfs_stats->avail = ((uintmax_t) fs_use.fsu_bfree * fs_use.fsu_blocksize) >> 10;
1739         myfs_stats->total = ((uintmax_t) fs_use.fsu_blocks * fs_use.fsu_blocksize) >> 10;
1740         myfs_stats->nfree = (uintmax_t) fs_use.fsu_ffree;
1741         myfs_stats->nodes = (uintmax_t) fs_use.fsu_files;
1742     }
1743     else
1744 #endif
1745     {
1746         myfs_stats->type = 0;
1747         myfs_stats->mpoint = "unknown";
1748         myfs_stats->device = "unknown";
1749         myfs_stats->avail = 0;
1750         myfs_stats->total = 0;
1751         myfs_stats->nfree = 0;
1752         myfs_stats->nodes = 0;
1753     }
1754 }
1755 
1756 /* --------------------------------------------------------------------------------------------- */

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