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

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