| /* mountlist.c -- return a list of mounted file systems | |
| Copyright (C) 1991-1992, 1997-2026 Free Software Foundation, Inc. | |
| This program is free software: you can redistribute it and/or modify | |
| it under the terms of the GNU General Public License as published by | |
| the Free Software Foundation, either version 3 of the License, or | |
| (at your option) any later version. | |
| This program is distributed in the hope that it will be useful, | |
| but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| GNU General Public License for more details. | |
| You should have received a copy of the GNU General Public License | |
| along with this program. If not, see <https://www.gnu.org/licenses/>. */ | |
| #include <config.h> | |
| #include "mountlist.h" | |
| #include <errno.h> | |
| #include <fcntl.h> | |
| #include <limits.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <stdint.h> | |
| #include <unistd.h> | |
| #include "c-ctype.h" | |
| #include "xalloc.h" | |
| #if HAVE_SYS_PARAM_H | |
| # include <sys/param.h> | |
| #endif | |
| #if MAJOR_IN_MKDEV | |
| # include <sys/mkdev.h> | |
| #elif MAJOR_IN_SYSMACROS | |
| # include <sys/sysmacros.h> | |
| #endif | |
| #if defined MOUNTED_GETFSSTAT /* (obsolete) Apple Darwin 1.3 */ | |
| # if HAVE_SYS_UCRED_H | |
| # include <grp.h> /* needed on OSF V4.0 for definition of NGROUPS, | |
| NGROUPS is used as an array dimension in ucred.h */ | |
| # include <sys/ucred.h> /* needed by powerpc-apple-darwin1.3.7 */ | |
| # endif | |
| # if HAVE_SYS_MOUNT_H | |
| # include <sys/mount.h> | |
| # endif | |
| # if HAVE_SYS_FS_TYPES_H | |
| # include <sys/fs_types.h> /* needed by powerpc-apple-darwin1.3.7 */ | |
| # endif | |
| # if HAVE_STRUCT_FSSTAT_F_FSTYPENAME | |
| # define FS_TYPE(Ent) ((Ent).f_fstypename) | |
| # else | |
| # define FS_TYPE(Ent) mnt_names[(Ent).f_type] | |
| # endif | |
| #endif /* MOUNTED_GETFSSTAT */ | |
| #ifdef MOUNTED_GETMNTENT1 /* glibc, HP-UX, Cygwin, Android, | |
| also (obsolete) 4.3BSD, SunOS */ | |
| # include <mntent.h> | |
| # include <sys/types.h> | |
| # if defined __ANDROID__ /* Android */ | |
| /* Bionic versions from between 2014-01-09 and 2015-01-08 define MOUNTED to | |
| an incorrect value; older Bionic versions don't define it at all. */ | |
| # undef MOUNTED | |
| # define MOUNTED "/proc/mounts" | |
| # elif !defined MOUNTED | |
| # if defined _PATH_MOUNTED /* GNU libc */ | |
| # define MOUNTED _PATH_MOUNTED | |
| # endif | |
| # if defined MNT_MNTTAB /* HP-UX. */ | |
| # define MOUNTED MNT_MNTTAB | |
| # endif | |
| # endif | |
| #endif | |
| #ifdef MOUNTED_GETMNTINFO /* Mac OS X, FreeBSD, OpenBSD, also (obsolete) 4.4BSD */ | |
| # include <sys/mount.h> | |
| #endif | |
| #ifdef MOUNTED_GETMNTINFO2 /* NetBSD, Minix */ | |
| # include <sys/statvfs.h> | |
| #endif | |
| #ifdef MOUNTED_FS_STAT_DEV /* Haiku, also (obsolete) BeOS */ | |
| # include <fs_info.h> | |
| # include <dirent.h> | |
| #endif | |
| #ifdef MOUNTED_FREAD_FSTYP /* (obsolete) SVR3 */ | |
| # include <mnttab.h> | |
| # include <sys/fstyp.h> | |
| # include <sys/statfs.h> | |
| #endif | |
| #ifdef MOUNTED_GETEXTMNTENT /* Solaris >= 8 */ | |
| # include <sys/mnttab.h> | |
| #endif | |
| #ifdef MOUNTED_GETMNTENT2 /* Solaris < 8, also (obsolete) SVR4 */ | |
| # include <sys/mnttab.h> | |
| #endif | |
| #ifdef MOUNTED_VMOUNT /* AIX */ | |
| # include <fshelp.h> | |
| # include <sys/vfs.h> | |
| #endif | |
| #ifdef MOUNTED_INTERIX_STATVFS /* Interix */ | |
| # include <sys/statvfs.h> | |
| # include <dirent.h> | |
| #endif | |
| #if HAVE_SYS_MNTENT_H | |
| /* This is to get MNTOPT_IGNORE on e.g. SVR4. */ | |
| # include <sys/mntent.h> | |
| #endif | |
| #ifdef MOUNTED_GETMNTENT1 | |
| # if !HAVE_SETMNTENT /* Android <= 4.4 */ | |
| # define setmntent(fp,mode) fopen (fp, mode "e") | |
| # endif | |
| # if !HAVE_ENDMNTENT /* Android <= 4.4 */ | |
| # define endmntent(fp) fclose (fp) | |
| # endif | |
| #endif | |
| #if defined _WIN32 && !defined __CYGWIN__ | |
| # include <windows.h> | |
| #endif | |
| #ifndef HAVE_HASMNTOPT | |
| # define hasmntopt(mnt, opt) ((char *) 0) | |
| #endif | |
| #undef MNT_IGNORE | |
| #ifdef MNTOPT_IGNORE | |
| # if defined __sun && defined __SVR4 | |
| /* Solaris defines hasmntopt(struct mnttab *, char *) | |
| while it is otherwise hasmntopt(struct mnttab *, const char *). */ | |
| # define MNT_IGNORE(M) hasmntopt (M, (char *) MNTOPT_IGNORE) | |
| # else | |
| # define MNT_IGNORE(M) hasmntopt (M, MNTOPT_IGNORE) | |
| # endif | |
| #else | |
| # define MNT_IGNORE(M) 0 | |
| #endif | |
| /* Each of the FILE streams in this file is only used in a single thread. */ | |
| #include "unlocked-io.h" | |
| /* The results of opendir() in this file are not used with dirfd and fchdir, | |
| therefore save some unnecessary work in fchdir.c. */ | |
| #ifdef GNULIB_defined_DIR | |
| # undef DIR | |
| # undef opendir | |
| # undef closedir | |
| # undef readdir | |
| #else | |
| # ifdef GNULIB_defined_opendir | |
| # undef opendir | |
| # endif | |
| # ifdef GNULIB_defined_closedir | |
| # undef closedir | |
| # endif | |
| #endif | |
| #define ME_DUMMY_0(Fs_name, Fs_type) \ | |
| (streq (Fs_type, "autofs") \ | |
| || streq (Fs_type, "proc") \ | |
| || streq (Fs_type, "subfs") \ | |
| /* for Linux 2.6/3.x */ \ | |
| || streq (Fs_type, "debugfs") \ | |
| || streq (Fs_type, "devpts") \ | |
| || streq (Fs_type, "fusectl") \ | |
| || streq (Fs_type, "fuse.portal") \ | |
| || streq (Fs_type, "mqueue") \ | |
| || streq (Fs_type, "rpc_pipefs") \ | |
| || streq (Fs_type, "sysfs") \ | |
| /* FreeBSD, Linux 2.4 */ \ | |
| || streq (Fs_type, "devfs") \ | |
| /* for NetBSD 3.0 */ \ | |
| || streq (Fs_type, "kernfs")) | |
| /* Historically, we have marked as "dummy" any file system of type "none", | |
| but now that programs like du need to know about bind-mounted directories, | |
| we grant an exception to any with "bind" in its list of mount options. | |
| I.e., those are *not* dummy entries. */ | |
| #ifdef MOUNTED_GETMNTENT1 | |
| # define ME_DUMMY(Fs_name, Fs_type, Bind) \ | |
| (ME_DUMMY_0 (Fs_name, Fs_type) \ | |
| || (streq (Fs_type, "none") && !Bind)) | |
| #else | |
| # define ME_DUMMY(Fs_name, Fs_type) \ | |
| (ME_DUMMY_0 (Fs_name, Fs_type) || streq (Fs_type, "none")) | |
| #endif | |
| #ifdef __CYGWIN__ | |
| # include <windows.h> | |
| /* Don't assume that UNICODE is not defined. */ | |
| # undef GetDriveType | |
| # define GetDriveType GetDriveTypeA | |
| # define ME_REMOTE me_remote | |
| /* All cygwin mount points include ':' or start with '//'; so it | |
| requires a native Windows call to determine remote disks. */ | |
| static bool | |
| me_remote (char const *fs_name, _GL_UNUSED char const *fs_type) | |
| { | |
| if (fs_name[0] && fs_name[1] == ':') | |
| { | |
| char drive[4]; | |
| sprintf (drive, "%c:\\", fs_name[0]); | |
| switch (GetDriveType (drive)) | |
| { | |
| case DRIVE_REMOVABLE: | |
| case DRIVE_FIXED: | |
| case DRIVE_CDROM: | |
| case DRIVE_RAMDISK: | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| #endif | |
| #ifndef ME_REMOTE | |
| /* A file system is "remote" if its Fs_name contains a ':' | |
| or if (it is of type (smbfs or cifs) and its Fs_name starts with '//') | |
| or if it is of any other of the listed types | |
| or Fs_name is equal to "-hosts" (used by autofs to mount remote fs). | |
| "VM" file systems like prl_fs or vboxsf are not considered remote here. */ | |
| # define ME_REMOTE(Fs_name, Fs_type) \ | |
| (strchr (Fs_name, ':') != NULL \ | |
| || ((Fs_name)[0] == '/' \ | |
| && (Fs_name)[1] == '/' \ | |
| && (streq (Fs_type, "smbfs") \ | |
| || streq (Fs_type, "smb3") \ | |
| || streq (Fs_type, "cifs"))) \ | |
| || streq (Fs_type, "acfs") \ | |
| || streq (Fs_type, "afs") \ | |
| || streq (Fs_type, "coda") \ | |
| || streq (Fs_type, "auristorfs") \ | |
| || streq (Fs_type, "fhgfs") \ | |
| || streq (Fs_type, "gpfs") \ | |
| || streq (Fs_type, "ibrix") \ | |
| || streq (Fs_type, "ocfs2") \ | |
| || streq (Fs_type, "vxfs") \ | |
| || streq ("-hosts", Fs_name)) | |
| #endif | |
| #if MOUNTED_GETMNTINFO /* Mac OS X, FreeBSD, OpenBSD, also (obsolete) 4.4BSD */ | |
| # if ! HAVE_STRUCT_STATFS_F_FSTYPENAME | |
| static char * | |
| fstype_to_string (short int t) | |
| { | |
| switch (t) | |
| { | |
| # ifdef MOUNT_PC | |
| case MOUNT_PC: | |
| return "pc"; | |
| # endif | |
| # ifdef MOUNT_MFS | |
| case MOUNT_MFS: | |
| return "mfs"; | |
| # endif | |
| # ifdef MOUNT_LO | |
| case MOUNT_LO: | |
| return "lo"; | |
| # endif | |
| # ifdef MOUNT_TFS | |
| case MOUNT_TFS: | |
| return "tfs"; | |
| # endif | |
| # ifdef MOUNT_TMP | |
| case MOUNT_TMP: | |
| return "tmp"; | |
| # endif | |
| # ifdef MOUNT_UFS | |
| case MOUNT_UFS: | |
| return "ufs" ; | |
| # endif | |
| # ifdef MOUNT_NFS | |
| case MOUNT_NFS: | |
| return "nfs" ; | |
| # endif | |
| # ifdef MOUNT_MSDOS | |
| case MOUNT_MSDOS: | |
| return "msdos" ; | |
| # endif | |
| # ifdef MOUNT_LFS | |
| case MOUNT_LFS: | |
| return "lfs" ; | |
| # endif | |
| # ifdef MOUNT_LOFS | |
| case MOUNT_LOFS: | |
| return "lofs" ; | |
| # endif | |
| # ifdef MOUNT_FDESC | |
| case MOUNT_FDESC: | |
| return "fdesc" ; | |
| # endif | |
| # ifdef MOUNT_PORTAL | |
| case MOUNT_PORTAL: | |
| return "portal" ; | |
| # endif | |
| # ifdef MOUNT_NULL | |
| case MOUNT_NULL: | |
| return "null" ; | |
| # endif | |
| # ifdef MOUNT_UMAP | |
| case MOUNT_UMAP: | |
| return "umap" ; | |
| # endif | |
| # ifdef MOUNT_KERNFS | |
| case MOUNT_KERNFS: | |
| return "kernfs" ; | |
| # endif | |
| # ifdef MOUNT_PROCFS | |
| case MOUNT_PROCFS: | |
| return "procfs" ; | |
| # endif | |
| # ifdef MOUNT_AFS | |
| case MOUNT_AFS: | |
| return "afs" ; | |
| # endif | |
| # ifdef MOUNT_CD9660 | |
| case MOUNT_CD9660: | |
| return "cd9660" ; | |
| # endif | |
| # ifdef MOUNT_UNION | |
| case MOUNT_UNION: | |
| return "union" ; | |
| # endif | |
| # ifdef MOUNT_DEVFS | |
| case MOUNT_DEVFS: | |
| return "devfs" ; | |
| # endif | |
| # ifdef MOUNT_EXT2FS | |
| case MOUNT_EXT2FS: | |
| return "ext2fs" ; | |
| # endif | |
| default: | |
| return "?"; | |
| } | |
| } | |
| # endif | |
| static char * | |
| fsp_to_string (const struct statfs *fsp) | |
| { | |
| # if HAVE_STRUCT_STATFS_F_FSTYPENAME | |
| return (char *) (fsp->f_fstypename); | |
| # else | |
| return fstype_to_string (fsp->f_type); | |
| # endif | |
| } | |
| #endif /* MOUNTED_GETMNTINFO */ | |
| #ifdef MOUNTED_VMOUNT /* AIX */ | |
| static char * | |
| fstype_to_string (int t) | |
| { | |
| struct vfs_ent *e = getvfsbytype (t); | |
| if (!e || !e->vfsent_name) | |
| return "none"; | |
| else | |
| return e->vfsent_name; | |
| } | |
| #endif /* MOUNTED_VMOUNT */ | |
| #if defined MOUNTED_GETMNTENT1 || defined MOUNTED_GETMNTENT2 | |
| /* Return the device number from MOUNT_OPTIONS, if possible. | |
| Otherwise return (dev_t) -1. */ | |
| static dev_t | |
| dev_from_mount_options (char const *mount_options) | |
| { | |
| /* GNU/Linux allows file system implementations to define their own | |
| meaning for "dev=" mount options, so don't trust the meaning | |
| here. */ | |
| # ifndef __linux__ | |
| static char const dev_pattern[] = ",dev="; | |
| char const *devopt = strstr (mount_options, dev_pattern); | |
| if (devopt) | |
| { | |
| char const *optval = devopt + sizeof dev_pattern - 1; | |
| if (c_isxdigit (*optval)) | |
| { | |
| errno = 0; | |
| char *optvalend; | |
| unsigned long int dev = strtoul (optval, &optvalend, 16); | |
| if (optval != optvalend | |
| && (*optvalend == '\0' || *optvalend == ',') | |
| && ! (dev == ULONG_MAX && errno == ERANGE) | |
| && dev == (dev_t) dev) | |
| return dev; | |
| } | |
| } | |
| # endif | |
| (void) mount_options; | |
| return -1; | |
| } | |
| #endif | |
| #if defined MOUNTED_GETMNTENT1 && (defined __linux__ || defined __ANDROID__) /* GNU/Linux, Android */ | |
| /* Unescape the paths in mount tables. | |
| STR is updated in place. */ | |
| static void | |
| unescape_tab (char *str) | |
| { | |
| size_t j = 0; | |
| size_t len = strlen (str) + 1; | |
| for (size_t i = 0; i < len; i++) | |
| { | |
| if (str[i] == '\\' && (i + 4 < len) | |
| && str[i + 1] >= '0' && str[i + 1] <= '3' | |
| && str[i + 2] >= '0' && str[i + 2] <= '7' | |
| && str[i + 3] >= '0' && str[i + 3] <= '7') | |
| { | |
| str[j++] = (str[i + 1] - '0') * 64 + | |
| (str[i + 2] - '0') * 8 + | |
| (str[i + 3] - '0'); | |
| i += 3; | |
| } | |
| else | |
| str[j++] = str[i]; | |
| } | |
| } | |
| /* Find the next space in STR, terminate the string there in place, | |
| and return that position. Otherwise return NULL. */ | |
| static char * | |
| terminate_at_blank (char *str) | |
| { | |
| char *s = strchr (str, ' '); | |
| if (s) | |
| *s = '\0'; | |
| return s; | |
| } | |
| #endif | |
| struct mount_entry * | |
| read_file_system_list (bool need_fs_type) | |
| { | |
| struct mount_entry *mount_list; | |
| struct mount_entry *me; | |
| struct mount_entry **mtail = &mount_list; | |
| (void) need_fs_type; | |
| #ifdef MOUNTED_GETMNTENT1 /* glibc, HP-UX, Cygwin, Android, | |
| also (obsolete) 4.3BSD, SunOS */ | |
| { | |
| # if defined __linux__ || defined __ANDROID__ | |
| /* Try parsing mountinfo first, as that make device IDs available. | |
| Note we could use libmount routines to simplify this parsing a little | |
| (and that code is in previous versions of this function), however | |
| libmount depends on libselinux which pulls in many dependencies. */ | |
| char const *mountinfo = "/proc/self/mountinfo"; | |
| FILE *fp = fopen (mountinfo, "re"); | |
| if (fp != NULL) | |
| { | |
| char *line = NULL; | |
| size_t buf_size = 0; | |
| while (getline (&line, &buf_size, fp) != -1) | |
| { | |
| unsigned int devmaj, devmin; | |
| int rc, mntroot_s; | |
| rc = sscanf(line, "%*u " /* id - discarded */ | |
| "%*u " /* parent - discarded */ | |
| "%u:%u " /* dev major:minor */ | |
| "%n", /* mountroot (start) */ | |
| &devmaj, &devmin, | |
| &mntroot_s); | |
| if (rc == 2 || rc == 3) /* 3 if %n included in count. */ | |
| { | |
| /* find end of MNTROOT. */ | |
| char *mntroot = line + mntroot_s; | |
| char *blank = terminate_at_blank (mntroot); | |
| if (blank) | |
| { | |
| /* find end of TARGET. */ | |
| char *target = blank + 1; | |
| blank = terminate_at_blank (target); | |
| if (blank) | |
| { | |
| /* skip optional fields, terminated by " - " */ | |
| char *dash = strstr (blank + 1, " - "); | |
| if (dash) | |
| { | |
| /* advance past the " - " separator. */ | |
| char *fstype = dash + 3; | |
| blank = terminate_at_blank (fstype); | |
| if (blank) | |
| { | |
| /* find end of SOURCE. */ | |
| char *source = blank + 1; | |
| if (terminate_at_blank (source)) | |
| { | |
| /* manipulate the sub-strings in place. */ | |
| unescape_tab (source); | |
| unescape_tab (target); | |
| unescape_tab (mntroot); | |
| unescape_tab (fstype); | |
| me = xmalloc (sizeof *me); | |
| me->me_devname = xstrdup (source); | |
| me->me_mountdir = xstrdup (target); | |
| me->me_mntroot = xstrdup (mntroot); | |
| me->me_type = xstrdup (fstype); | |
| me->me_type_malloced = 1; | |
| me->me_dev = makedev (devmaj, devmin); | |
| /* we pass "false" for the "Bind" option as that's only | |
| significant when the Fs_type is "none" which will not be | |
| the case when parsing "/proc/self/mountinfo", and only | |
| applies for static /etc/mtab files. */ | |
| me->me_dummy = ME_DUMMY (me->me_devname, me->me_type, false); | |
| me->me_remote = ME_REMOTE (me->me_devname, me->me_type); | |
| /* Add to the linked list. */ | |
| *mtail = me; | |
| mtail = &me->me_next; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| free (line); | |
| if (ferror (fp)) | |
| { | |
| int saved_errno = errno; | |
| fclose (fp); | |
| errno = saved_errno; | |
| goto free_then_fail; | |
| } | |
| if (fclose (fp) == EOF) | |
| goto free_then_fail; | |
| } | |
| else /* fallback to /proc/self/mounts (/etc/mtab). */ | |
| # endif /* __linux__ || __ANDROID__ */ | |
| { | |
| char const *table = MOUNTED; | |
| FILE *mfp = setmntent (table, "r"); | |
| if (mfp == NULL) | |
| return NULL; | |
| struct mntent *mnt; | |
| while ((mnt = getmntent (mfp))) | |
| { | |
| bool bind = hasmntopt (mnt, "bind"); | |
| me = xmalloc (sizeof *me); | |
| me->me_devname = xstrdup (mnt->mnt_fsname); | |
| me->me_mountdir = xstrdup (mnt->mnt_dir); | |
| me->me_mntroot = NULL; | |
| me->me_type = xstrdup (mnt->mnt_type); | |
| me->me_type_malloced = 1; | |
| me->me_dummy = ME_DUMMY (me->me_devname, me->me_type, bind); | |
| me->me_remote = ME_REMOTE (me->me_devname, me->me_type); | |
| me->me_dev = dev_from_mount_options (mnt->mnt_opts); | |
| /* Add to the linked list. */ | |
| *mtail = me; | |
| mtail = &me->me_next; | |
| } | |
| if (endmntent (mfp) == 0) | |
| goto free_then_fail; | |
| } | |
| } | |
| #endif /* MOUNTED_GETMNTENT1. */ | |
| #ifdef MOUNTED_GETMNTINFO /* Mac OS X, FreeBSD, OpenBSD, also (obsolete) 4.4BSD */ | |
| { | |
| struct statfs *fsp; | |
| int entries = getmntinfo (&fsp, MNT_NOWAIT); | |
| if (entries < 0) | |
| return NULL; | |
| for (; entries-- > 0; fsp++) | |
| { | |
| char *fs_type = fsp_to_string (fsp); | |
| me = xmalloc (sizeof *me); | |
| me->me_devname = xstrdup (fsp->f_mntfromname); | |
| me->me_mountdir = xstrdup (fsp->f_mntonname); | |
| me->me_mntroot = NULL; | |
| me->me_type = fs_type; | |
| me->me_type_malloced = 0; | |
| me->me_dummy = ME_DUMMY (me->me_devname, me->me_type); | |
| me->me_remote = ME_REMOTE (me->me_devname, me->me_type); | |
| me->me_dev = (dev_t) -1; /* Magic; means not known yet. */ | |
| /* Add to the linked list. */ | |
| *mtail = me; | |
| mtail = &me->me_next; | |
| } | |
| } | |
| #endif /* MOUNTED_GETMNTINFO */ | |
| #ifdef MOUNTED_GETMNTINFO2 /* NetBSD, Minix */ | |
| { | |
| struct statvfs *fsp; | |
| int entries = getmntinfo (&fsp, MNT_NOWAIT); | |
| if (entries < 0) | |
| return NULL; | |
| for (; entries-- > 0; fsp++) | |
| { | |
| me = xmalloc (sizeof *me); | |
| me->me_devname = xstrdup (fsp->f_mntfromname); | |
| me->me_mountdir = xstrdup (fsp->f_mntonname); | |
| me->me_mntroot = NULL; | |
| me->me_type = xstrdup (fsp->f_fstypename); | |
| me->me_type_malloced = 1; | |
| me->me_dummy = ME_DUMMY (me->me_devname, me->me_type); | |
| me->me_remote = ME_REMOTE (me->me_devname, me->me_type); | |
| me->me_dev = (dev_t) -1; /* Magic; means not known yet. */ | |
| /* Add to the linked list. */ | |
| *mtail = me; | |
| mtail = &me->me_next; | |
| } | |
| } | |
| #endif /* MOUNTED_GETMNTINFO2 */ | |
| #if defined MOUNTED_FS_STAT_DEV /* Haiku, also (obsolete) BeOS */ | |
| { | |
| /* The next_dev() and fs_stat_dev() system calls give the list of | |
| all file systems, including the information returned by statvfs() | |
| (fs type, total blocks, free blocks etc.), but without the mount | |
| point. But on BeOS all file systems except / are mounted in the | |
| rootfs, directly under /. | |
| The directory name of the mount point is often, but not always, | |
| identical to the volume name of the device. | |
| We therefore get the list of subdirectories of /, and the list | |
| of all file systems, and match the two lists. */ | |
| struct rootdir_entry | |
| { | |
| char *name; | |
| dev_t dev; | |
| ino_t ino; | |
| struct rootdir_entry *next; | |
| }; | |
| /* All volumes are mounted in the rootfs, directly under /. */ | |
| struct rootdir_entry *rootdir_list = NULL; | |
| struct rootdir_entry **rootdir_tail = &rootdir_list; | |
| DIR *dirp = opendir ("/"); | |
| if (dirp) | |
| { | |
| struct dirent *d; | |
| while ((d = readdir (dirp)) != NULL) | |
| { | |
| char *name; | |
| struct stat statbuf; | |
| if (! streq (d->d_name, "..")) | |
| { | |
| if (streq (d->d_name, ".")) | |
| name = xstrdup ("/"); | |
| else | |
| { | |
| name = xmalloc (1 + strlen (d->d_name) + 1); | |
| name[0] = '/'; | |
| strcpy (name + 1, d->d_name); | |
| } | |
| if (lstat (name, &statbuf) >= 0 && S_ISDIR (statbuf.st_mode)) | |
| { | |
| struct rootdir_entry *re = xmalloc (sizeof *re); | |
| re->name = name; | |
| re->dev = statbuf.st_dev; | |
| re->ino = statbuf.st_ino; | |
| /* Add to the linked list. */ | |
| *rootdir_tail = re; | |
| rootdir_tail = &re->next; | |
| } | |
| else | |
| free (name); | |
| } | |
| } | |
| closedir (dirp); | |
| } | |
| *rootdir_tail = NULL; | |
| dev_t dev; | |
| for (int32 pos = 0; (dev = next_dev (&pos)) >= 0; ) | |
| { | |
| fs_info fi; | |
| if (fs_stat_dev (dev, &fi) >= 0) | |
| { | |
| /* Note: fi.dev == dev. */ | |
| struct rootdir_entry *re; | |
| for (re = rootdir_list; re; re = re->next) | |
| if (re->dev == fi.dev && re->ino == fi.root) | |
| break; | |
| me = xmalloc (sizeof *me); | |
| me->me_devname = xstrdup (fi.device_name[0] != '\0' | |
| ? fi.device_name : fi.fsh_name); | |
| me->me_mountdir = xstrdup (re != NULL ? re->name : fi.fsh_name); | |
| me->me_mntroot = NULL; | |
| me->me_type = xstrdup (fi.fsh_name); | |
| me->me_type_malloced = 1; | |
| me->me_dev = fi.dev; | |
| me->me_dummy = 0; | |
| me->me_remote = (fi.flags & B_FS_IS_SHARED) != 0; | |
| /* Add to the linked list. */ | |
| *mtail = me; | |
| mtail = &me->me_next; | |
| } | |
| } | |
| *mtail = NULL; | |
| while (rootdir_list != NULL) | |
| { | |
| struct rootdir_entry *re = rootdir_list; | |
| rootdir_list = re->next; | |
| free (re->name); | |
| free (re); | |
| } | |
| } | |
| #endif /* MOUNTED_FS_STAT_DEV */ | |
| #if defined MOUNTED_GETFSSTAT /* (obsolete) Apple Darwin 1.3 */ | |
| { | |
| int numsys = getfsstat (NULL, 0L, MNT_NOWAIT); | |
| if (numsys < 0) | |
| return NULL; | |
| struct statfs *stats; | |
| if (SIZE_MAX / sizeof *stats <= numsys) | |
| xalloc_die (); | |
| size_t bufsize = (1 + numsys) * sizeof *stats; | |
| stats = xmalloc (bufsize); | |
| numsys = getfsstat (stats, bufsize, MNT_NOWAIT); | |
| if (numsys < 0) | |
| { | |
| free (stats); | |
| return NULL; | |
| } | |
| for (int counter = 0; counter < numsys; counter++) | |
| { | |
| me = xmalloc (sizeof *me); | |
| me->me_devname = xstrdup (stats[counter].f_mntfromname); | |
| me->me_mountdir = xstrdup (stats[counter].f_mntonname); | |
| me->me_mntroot = NULL; | |
| me->me_type = xstrdup (FS_TYPE (stats[counter])); | |
| me->me_type_malloced = 1; | |
| me->me_dummy = ME_DUMMY (me->me_devname, me->me_type); | |
| me->me_remote = ME_REMOTE (me->me_devname, me->me_type); | |
| me->me_dev = (dev_t) -1; /* Magic; means not known yet. */ | |
| /* Add to the linked list. */ | |
| *mtail = me; | |
| mtail = &me->me_next; | |
| } | |
| free (stats); | |
| } | |
| #endif /* MOUNTED_GETFSSTAT */ | |
| #if defined MOUNTED_FREAD_FSTYP /* (obsolete) SVR3 */ | |
| { | |
| char *table = "/etc/mnttab"; | |
| FILE *fp = fopen (table, "re"); | |
| if (fp == NULL) | |
| return NULL; | |
| struct mnttab mnt; | |
| while (fread (&mnt, sizeof mnt, 1, fp) > 0) | |
| { | |
| me = xmalloc (sizeof *me); | |
| me->me_devname = xstrdup (mnt.mt_dev); | |
| me->me_mountdir = xstrdup (mnt.mt_filsys); | |
| me->me_mntroot = NULL; | |
| me->me_dev = (dev_t) -1; /* Magic; means not known yet. */ | |
| me->me_type = ""; | |
| me->me_type_malloced = 0; | |
| if (need_fs_type) | |
| { | |
| struct statfs fsd; | |
| char typebuf[FSTYPSZ]; | |
| if (statfs (me->me_mountdir, &fsd, sizeof fsd, 0) != -1 | |
| && sysfs (GETFSTYP, fsd.f_fstyp, typebuf) != -1) | |
| { | |
| me->me_type = xstrdup (typebuf); | |
| me->me_type_malloced = 1; | |
| } | |
| } | |
| me->me_dummy = ME_DUMMY (me->me_devname, me->me_type); | |
| me->me_remote = ME_REMOTE (me->me_devname, me->me_type); | |
| /* Add to the linked list. */ | |
| *mtail = me; | |
| mtail = &me->me_next; | |
| } | |
| if (ferror (fp)) | |
| { | |
| /* The last fread() call must have failed. */ | |
| int saved_errno = errno; | |
| fclose (fp); | |
| errno = saved_errno; | |
| goto free_then_fail; | |
| } | |
| if (fclose (fp) == EOF) | |
| goto free_then_fail; | |
| } | |
| #endif /* MOUNTED_FREAD_FSTYP. */ | |
| #ifdef MOUNTED_GETEXTMNTENT /* Solaris >= 8 */ | |
| { | |
| const char *table = MNTTAB; | |
| /* No locking is needed, because the contents of /etc/mnttab is generated | |
| by the kernel. */ | |
| errno = 0; | |
| FILE *fp = fopen (table, "re"); | |
| int ret; | |
| if (fp == NULL) | |
| ret = errno; | |
| else | |
| { | |
| struct extmnttab mnt; | |
| while ((ret = getextmntent (fp, &mnt, 1)) == 0) | |
| { | |
| me = xmalloc (sizeof *me); | |
| me->me_devname = xstrdup (mnt.mnt_special); | |
| me->me_mountdir = xstrdup (mnt.mnt_mountp); | |
| me->me_mntroot = NULL; | |
| me->me_type = xstrdup (mnt.mnt_fstype); | |
| me->me_type_malloced = 1; | |
| /* The cast from 'struct extmnttab *' to 'struct mnttab *' is OK | |
| because 'struct extmnttab' extends 'struct mnttab'. */ | |
| me->me_dummy = MNT_IGNORE ((struct mnttab *) &mnt) != 0; | |
| me->me_remote = ME_REMOTE (me->me_devname, me->me_type); | |
| me->me_dev = makedev (mnt.mnt_major, mnt.mnt_minor); | |
| /* Add to the linked list. */ | |
| *mtail = me; | |
| mtail = &me->me_next; | |
| } | |
| ret = fclose (fp) == EOF ? errno : 0 < ret ? 0 : -1; | |
| /* Here ret = -1 means success, ret >= 0 means failure. */ | |
| } | |
| if (0 <= ret) | |
| { | |
| errno = ret; | |
| goto free_then_fail; | |
| } | |
| } | |
| #endif /* MOUNTED_GETEXTMNTENT */ | |
| #ifdef MOUNTED_GETMNTENT2 /* Solaris < 8, also (obsolete) SVR4 */ | |
| { | |
| const char *table = MNTTAB; | |
| int lockfd = -1; | |
| # if defined F_RDLCK && defined F_SETLKW | |
| /* MNTTAB_LOCK is a macro name of our own invention; it's not present in | |
| e.g. Solaris 2.6. If the SVR4 folks ever define a macro | |
| for this file name, we should use their macro name instead. | |
| (Why not just lock MNTTAB directly? We don't know.) */ | |
| # ifndef MNTTAB_LOCK | |
| # define MNTTAB_LOCK "/etc/.mnttab.lock" | |
| # endif | |
| lockfd = open (MNTTAB_LOCK, O_RDONLY | O_CLOEXEC); | |
| if (0 <= lockfd) | |
| { | |
| struct flock flock; | |
| flock.l_type = F_RDLCK; | |
| flock.l_whence = SEEK_SET; | |
| flock.l_start = 0; | |
| flock.l_len = 0; | |
| while (fcntl (lockfd, F_SETLKW, &flock) == -1) | |
| if (errno != EINTR) | |
| { | |
| int saved_errno = errno; | |
| close (lockfd); | |
| errno = saved_errno; | |
| return NULL; | |
| } | |
| } | |
| else if (errno != ENOENT) | |
| return NULL; | |
| # endif | |
| errno = 0; | |
| FILE *fp = fopen (table, "re"); | |
| int ret; | |
| if (fp == NULL) | |
| ret = errno; | |
| else | |
| { | |
| struct mnttab mnt; | |
| while ((ret = getmntent (fp, &mnt)) == 0) | |
| { | |
| me = xmalloc (sizeof *me); | |
| me->me_devname = xstrdup (mnt.mnt_special); | |
| me->me_mountdir = xstrdup (mnt.mnt_mountp); | |
| me->me_mntroot = NULL; | |
| me->me_type = xstrdup (mnt.mnt_fstype); | |
| me->me_type_malloced = 1; | |
| me->me_dummy = MNT_IGNORE (&mnt) != 0; | |
| me->me_remote = ME_REMOTE (me->me_devname, me->me_type); | |
| me->me_dev = dev_from_mount_options (mnt.mnt_mntopts); | |
| /* Add to the linked list. */ | |
| *mtail = me; | |
| mtail = &me->me_next; | |
| } | |
| ret = fclose (fp) == EOF ? errno : 0 < ret ? 0 : -1; | |
| /* Here ret = -1 means success, ret >= 0 means failure. */ | |
| } | |
| if (0 <= lockfd && close (lockfd) != 0) | |
| ret = errno; | |
| if (0 <= ret) | |
| { | |
| errno = ret; | |
| goto free_then_fail; | |
| } | |
| } | |
| #endif /* MOUNTED_GETMNTENT2. */ | |
| #ifdef MOUNTED_VMOUNT /* AIX */ | |
| { | |
| /* Ask how many bytes to allocate for the mounted file system info. */ | |
| int bufsize; | |
| if (mntctl (MCTL_QUERY, sizeof bufsize, (char *) &bufsize) != 0) | |
| return NULL; | |
| void *entries = xmalloc (bufsize); | |
| /* Get the list of mounted file systems. */ | |
| int n_entries = mntctl (MCTL_QUERY, bufsize, entries); | |
| if (n_entries < 0) | |
| { | |
| free (entries); | |
| return NULL; | |
| } | |
| char *thisent = entries; | |
| for (int i = 0; i < n_entries; i++) | |
| { | |
| struct vmount *vmp = (struct vmount *) thisent; | |
| me = xmalloc (sizeof *me); | |
| if (vmp->vmt_flags & MNT_REMOTE) | |
| { | |
| char *host, *dir; | |
| me->me_remote = 1; | |
| /* Prepend the remote dirname. */ | |
| host = thisent + vmp->vmt_data[VMT_HOSTNAME].vmt_off; | |
| dir = thisent + vmp->vmt_data[VMT_OBJECT].vmt_off; | |
| me->me_devname = xmalloc (strlen (host) + strlen (dir) + 2); | |
| strcpy (me->me_devname, host); | |
| strcat (me->me_devname, ":"); | |
| strcat (me->me_devname, dir); | |
| } | |
| else | |
| { | |
| me->me_remote = 0; | |
| me->me_devname = xstrdup (thisent + | |
| vmp->vmt_data[VMT_OBJECT].vmt_off); | |
| } | |
| me->me_mountdir = xstrdup (thisent + vmp->vmt_data[VMT_STUB].vmt_off); | |
| me->me_mntroot = NULL; | |
| me->me_type = xstrdup (fstype_to_string (vmp->vmt_gfstype)); | |
| me->me_type_malloced = 1; | |
| char *options = thisent + vmp->vmt_data[VMT_ARGS].vmt_off; | |
| char *ignore = strstr (options, "ignore"); | |
| me->me_dummy = (ignore | |
| && (ignore == options || ignore[-1] == ',') | |
| && (ignore[sizeof "ignore" - 1] == ',' | |
| || ignore[sizeof "ignore" - 1] == '\0')); | |
| me->me_dev = (dev_t) -1; /* vmt_fsid might be the info we want. */ | |
| /* Add to the linked list. */ | |
| *mtail = me; | |
| mtail = &me->me_next; | |
| thisent += vmp->vmt_length; | |
| } | |
| free (entries); | |
| } | |
| #endif /* MOUNTED_VMOUNT. */ | |
| #ifdef MOUNTED_INTERIX_STATVFS /* Interix */ | |
| { | |
| DIR *dirp = opendir ("/dev/fs"); | |
| if (!dirp) | |
| goto free_then_fail; | |
| while (1) | |
| { | |
| /* FIXME: readdir_r is planned to be withdrawn from POSIX and | |
| marked obsolescent in glibc. Use readdir instead. */ | |
| struct dirent entry; | |
| struct dirent *result; | |
| if (readdir_r (dirp, &entry, &result) || result == NULL) | |
| break; | |
| char node[9 + NAME_MAX]; | |
| strcpy (node, "/dev/fs/"); | |
| strcat (node, entry.d_name); | |
| struct statvfs dev; | |
| if (statvfs (node, &dev) == 0) | |
| { | |
| me = xmalloc (sizeof *me); | |
| me->me_devname = xstrdup (dev.f_mntfromname); | |
| me->me_mountdir = xstrdup (dev.f_mntonname); | |
| me->me_mntroot = NULL; | |
| me->me_type = xstrdup (dev.f_fstypename); | |
| me->me_type_malloced = 1; | |
| me->me_dummy = ME_DUMMY (me->me_devname, me->me_type); | |
| me->me_remote = ME_REMOTE (me->me_devname, me->me_type); | |
| me->me_dev = (dev_t) -1; /* Magic; means not known yet. */ | |
| /* Add to the linked list. */ | |
| *mtail = me; | |
| mtail = &me->me_next; | |
| } | |
| } | |
| closedir (dirp); | |
| } | |
| #endif /* MOUNTED_INTERIX_STATVFS */ | |
| #if defined _WIN32 && !defined __CYGWIN__ /* native Windows */ | |
| /* Don't assume that UNICODE is not defined. */ | |
| # undef GetDriveType | |
| # define GetDriveType GetDriveTypeA | |
| # undef GetVolumeInformation | |
| # define GetVolumeInformation GetVolumeInformationA | |
| { | |
| /* Windows has drive prefixes which are similar to mount points. | |
| GetLogicalDrives returns a bitmask where the i-th bit is set | |
| if ASCII 'A' + i is an available drive. See: | |
| <https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getlogicaldrives>. */ | |
| DWORD value = GetLogicalDrives (); | |
| for (unsigned int i = 0; i < 26; ++i) | |
| { | |
| if (value & (1U << i)) | |
| { | |
| char mountdir[4]; | |
| mountdir[0] = 'A' + i; | |
| mountdir[1] = ':'; | |
| mountdir[2] = '\\'; | |
| mountdir[3] = '\0'; | |
| char fs_name[MAX_PATH + 1]; | |
| /* Test whether the drive actually exists, and | |
| get the name of the file system. See: | |
| <https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getvolumeinformationa>. */ | |
| if (GetVolumeInformation (mountdir, NULL, 0, NULL, NULL, NULL, | |
| fs_name, sizeof fs_name)) | |
| { | |
| me = xmalloc (sizeof *me); | |
| me->me_mountdir = xstrdup (mountdir); | |
| /* Check if drive is remote. See: | |
| <https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getdrivetypea>. */ | |
| me->me_remote = GetDriveType (mountdir) == DRIVE_REMOTE; | |
| /* Here we could use | |
| QueryDosDeviceW -> returns something like '\Device\HarddiskVolume2' | |
| GetVolumeNameForVolumeMountPointW -> return something like '\\?\Volume{...}' | |
| */ | |
| me->me_devname = NULL; | |
| { | |
| /* Find the SUBST or NET USE mapping of the given drive. | |
| <https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-querydosdevicew> | |
| For testing of SUBST: <https://ss64.com/nt/subst.html> | |
| For testing of NET USE: <https://ss64.com/nt/net-use.html> */ | |
| wchar_t drive[3]; | |
| drive[0] = L'A' + i; | |
| drive[1] = L':'; | |
| drive[2] = L'\0'; | |
| wchar_t mapping[MAX_PATH + 1]; | |
| DWORD mapping_len = QueryDosDeviceW (drive, mapping, sizeof (mapping) / sizeof (mapping[0])); | |
| if (mapping_len > 4 && wcsncmp (mapping, L"\\??\\", 4) == 0) | |
| { | |
| /* It's a SUBSTed drive. */ | |
| char subst_dir[MAX_PATH + 1]; | |
| size_t subst_dir_len = wcstombs (subst_dir, mapping + 4, sizeof (subst_dir)); | |
| if (subst_dir_len > 0 && subst_dir_len <= MAX_PATH) | |
| me->me_mntroot = xstrdup (subst_dir); | |
| else | |
| /* mapping is too long or not convertible to the | |
| locale encoding. */ | |
| me->me_mntroot = NULL; | |
| } | |
| else if (mapping_len > 26 | |
| && wcsncmp (mapping, L"\\Device\\LanmanRedirector\\;", 26) == 0) | |
| { | |
| wchar_t *next_backslash = wcschr (mapping + 26, L'\\'); | |
| if (next_backslash != NULL) | |
| { | |
| *--next_backslash = L'\\'; | |
| char share_dir[MAX_PATH + 1]; | |
| size_t share_dir_len = wcstombs (share_dir, next_backslash, sizeof (share_dir)); | |
| if (share_dir_len > 0 && share_dir_len <= MAX_PATH) | |
| me->me_mntroot = xstrdup (share_dir); | |
| else | |
| /* mapping is too long or not convertible to the | |
| locale encoding. */ | |
| me->me_mntroot = NULL; | |
| } | |
| else | |
| /* mapping does not have the expected form. */ | |
| me->me_mntroot = NULL; | |
| } | |
| else | |
| /* It's neither a SUBSTed nor a NET USEd drive. */ | |
| me->me_mntroot = NULL; | |
| } | |
| me->me_dev = (dev_t) -1; | |
| me->me_dummy = 0; | |
| me->me_type = xstrdup (fs_name); | |
| me->me_type_malloced = 1; | |
| /* Add to the linked list. */ | |
| *mtail = me; | |
| mtail = &me->me_next; | |
| } | |
| } | |
| } | |
| } | |
| { | |
| /* Windows also has true mount points, called "mounted folders". See | |
| <https://learn.microsoft.com/en-us/windows/win32/fileio/volume-mount-points> | |
| For testing: <https://learn.microsoft.com/en-us/windows-server/storage/disk-management/assign-a-mount-point-folder-path-to-a-drive> */ | |
| /* Enumerate the volumes. See | |
| <https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-findfirstvolumew> | |
| <https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-findnextvolumew> | |
| <https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-findvolumeclose> */ | |
| wchar_t vol_name[MAX_PATH + 1]; | |
| HANDLE h = FindFirstVolumeW (vol_name, sizeof (vol_name) / sizeof (vol_name[0])); | |
| if (h != INVALID_HANDLE_VALUE) | |
| { | |
| do | |
| { | |
| /* Look where the volume vol_name is mounted. | |
| There are two APIs for doing this: | |
| - FindFirstVolumeMountPointW, FindNextVolumeMountPointW, | |
| FindVolumeMountPointClose. This API always fails with | |
| error code ERROR_ACCESS_DENIED. | |
| - GetVolumePathNamesForVolumeNameW. This API works but | |
| may require a significantly larger buffer. | |
| <https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getvolumepathnamesforvolumenamew> */ | |
| wchar_t stack_buf[MAX_PATH + 2]; | |
| wchar_t *malloced_buf = NULL; | |
| wchar_t *buf = stack_buf; | |
| DWORD bufsize = sizeof (stack_buf) / sizeof (wchar_t); | |
| BOOL success; | |
| for (;;) | |
| { | |
| success = GetVolumePathNamesForVolumeNameW (vol_name, buf, bufsize, &bufsize); | |
| if (!success && GetLastError () == ERROR_MORE_DATA) | |
| { | |
| free (malloced_buf); | |
| malloced_buf = (wchar_t *) xmalloc (bufsize * sizeof (wchar_t)); | |
| buf = malloced_buf; | |
| } | |
| else | |
| break; | |
| } | |
| if (success) | |
| { | |
| wchar_t *mount_dir = buf; | |
| while (*mount_dir != L'\0') | |
| { | |
| /* Drive mounts are already handled above. */ | |
| if (!(mount_dir[0] >= L'A' && mount_dir[0] <= L'Z' | |
| && mount_dir[1] == L':' && mount_dir[2] == L'\\' | |
| && mount_dir[3] == L'\0')) | |
| { | |
| char mountdir[MAX_PATH + 1]; | |
| size_t mountdir_len = wcstombs (mountdir, mount_dir, sizeof (mountdir)); | |
| if (mountdir_len > 0 && mountdir_len <= MAX_PATH) | |
| { | |
| char fs_name[MAX_PATH + 1]; | |
| /* Get the name of the file system. See: | |
| <https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getvolumeinformationa>. */ | |
| if (GetVolumeInformation (mountdir, NULL, 0, NULL, NULL, NULL, | |
| fs_name, sizeof fs_name)) | |
| { | |
| me = xmalloc (sizeof *me); | |
| me->me_mountdir = xstrdup (mountdir); | |
| me->me_remote = false; | |
| /* Here we could use vol_name, something like '\\?\Volume{...}'. */ | |
| me->me_devname = NULL; | |
| me->me_mntroot = NULL; | |
| me->me_dev = (dev_t) -1; | |
| me->me_dummy = 0; | |
| me->me_type = xstrdup (fs_name); | |
| me->me_type_malloced = 1; | |
| /* Add to the linked list. */ | |
| *mtail = me; | |
| mtail = &me->me_next; | |
| } | |
| } | |
| else | |
| { | |
| /* mount_dir is too long or not convertible to the | |
| locale encoding. */ | |
| } | |
| } | |
| mount_dir += wcslen (mount_dir) + 1; | |
| } | |
| } | |
| free (malloced_buf); | |
| } | |
| while (FindNextVolumeW (h, vol_name, sizeof (vol_name) / sizeof (vol_name[0]))); | |
| FindVolumeClose (h); | |
| } | |
| } | |
| #endif | |
| #if MOUNTED_NOT_PORTED | |
| # error "Please port gnulib mountlist.c to your platform!" | |
| #endif | |
| *mtail = NULL; | |
| return mount_list; | |
| free_then_fail: _GL_UNUSED_LABEL; | |
| { | |
| int saved_errno = errno; | |
| *mtail = NULL; | |
| while (mount_list) | |
| { | |
| me = mount_list->me_next; | |
| free_mount_entry (mount_list); | |
| mount_list = me; | |
| } | |
| errno = saved_errno; | |
| return NULL; | |
| } | |
| } | |
| /* Free a mount entry as returned from read_file_system_list (). */ | |
| void | |
| free_mount_entry (struct mount_entry *me) | |
| { | |
| free (me->me_devname); | |
| free (me->me_mountdir); | |
| free (me->me_mntroot); | |
| if (me->me_type_malloced) | |
| free (me->me_type); | |
| free (me); | |
| } |