1#!/bin/sh 2# Test df's behavior when the mount list contains duplicate entries. 3# This test is skipped on systems that lack LD_PRELOAD support; that's fine. 4 5# Copyright (C) 2012-2023 Free Software Foundation, Inc. 6 7# This program is free software: you can redistribute it and/or modify 8# it under the terms of the GNU General Public License as published by 9# the Free Software Foundation, either version 3 of the License, or 10# (at your option) any later version. 11 12# This program is distributed in the hope that it will be useful, 13# but WITHOUT ANY WARRANTY; without even the implied warranty of 14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15# GNU General Public License for more details. 16 17# You should have received a copy of the GNU General Public License 18# along with this program. If not, see <https://www.gnu.org/licenses/>. 19 20. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src 21print_ver_ df 22require_gcc_shared_ 23 24# We use --local here so as to not activate 25# potentially very many remote mounts. 26df --local --output=target >LOCAL_FS || skip_ 'df fails' 27grep '^/$' LOCAL_FS || skip_ 'no root file system found' 28 29# Get real targets to substitute for /NONROOT and /REMOTE below. 30export CU_NONROOT_FS=$(grep /. LOCAL_FS | head -n1) 31export CU_REMOTE_FS=$(grep /. LOCAL_FS | tail -n+2 | head -n1) 32 33unique_entries=1 34test -z "$CU_NONROOT_FS" || unique_entries=$(expr $unique_entries + 1) 35test -z "$CU_REMOTE_FS" || unique_entries=$(expr $unique_entries + 2) 36 37grep '^#define HAVE_MNTENT_H 1' $CONFIG_HEADER > /dev/null \ 38 || skip_ "no mntent.h available to confirm the interface" 39 40grep '^#define HAVE_GETMNTENT 1' $CONFIG_HEADER > /dev/null \ 41 || skip_ "getmntent is not used on this system" 42 43# Simulate an mtab file to test various cases. 44cat > k.c <<EOF || framework_failure_ 45#define _GNU_SOURCE 46#include <stdio.h> 47#include <stdlib.h> 48#include <errno.h> 49#include <mntent.h> 50#include <string.h> 51#include <dlfcn.h> 52 53#define STREQ(a, b) (strcmp (a, b) == 0) 54 55FILE* fopen(const char *path, const char *mode) 56{ 57 static FILE* (*fopen_func)(char const *, char const *); 58 59 /* get reference to original (libc provided) fopen */ 60 if (!fopen_func) 61 { 62 fopen_func = (FILE*(*)(char const *, char const *)) 63 dlsym(RTLD_NEXT, "fopen"); 64 if (!fopen_func) 65 { 66 fprintf (stderr, "Failed to find fopen()\n"); 67 errno = ESRCH; 68 return NULL; 69 } 70 } 71 72 /* Returning ENOENT here will get read_file_system_list() 73 to fall back to using getmntent() below. */ 74 if (STREQ (path, "/proc/self/mountinfo")) 75 { 76 errno = ENOENT; 77 return NULL; 78 } 79 else 80 return fopen_func(path, mode); 81} 82 83#define STREQ(a, b) (strcmp (a, b) == 0) 84 85struct mntent *getmntent (FILE *fp) 86{ 87 static char *nonroot_fs; 88 static char *remote_fs; 89 static int done; 90 91 /* Prove that LD_PRELOAD works. */ 92 if (!done) 93 { 94 fclose (fopen ("x", "w")); 95 ++done; 96 } 97 98 static struct mntent mntents[] = { 99 {.mnt_fsname="/short", .mnt_dir="/invalid/mount/dir", .mnt_opts=""}, 100 {.mnt_fsname="fsname", .mnt_dir="/", .mnt_opts=""}, 101 {.mnt_fsname="/fsname", .mnt_dir="/.", .mnt_opts=""}, 102 {.mnt_fsname="/fsname", .mnt_dir="/", .mnt_opts=""}, 103 {.mnt_fsname="virtfs", .mnt_dir="/NONROOT", .mnt_type="t1", .mnt_opts=""}, 104 {.mnt_fsname="virtfs2", .mnt_dir="/NONROOT", .mnt_type="t2", .mnt_opts=""}, 105 {.mnt_fsname="netns", .mnt_dir="net:[1234567]", .mnt_opts=""}, 106 {.mnt_fsname="rem:ote1",.mnt_dir="/REMOTE", .mnt_opts=""}, 107 {.mnt_fsname="rem:ote1",.mnt_dir="/REMOTE", .mnt_opts=""}, 108 {.mnt_fsname="rem:ote2",.mnt_dir="/REMOTE", .mnt_opts=""}, 109 }; 110 111 if (done == 1) 112 { 113 nonroot_fs = getenv ("CU_NONROOT_FS"); 114 if (!nonroot_fs || !*nonroot_fs) 115 nonroot_fs = "/"; /* merge into / entries. */ 116 117 remote_fs = getenv ("CU_REMOTE_FS"); 118 } 119 120 if (done == 1 && !getenv ("CU_TEST_DUPE_INVALID")) 121 done++; /* skip the first entry. */ 122 123 while (done++ <= 10) 124 { 125 if (!mntents[done-2].mnt_type) 126 mntents[done-2].mnt_type = "-"; 127 if (!mntents[done-2].mnt_opts) 128 mntents[done-2].mnt_opts = "-"; 129 if (STREQ (mntents[done-2].mnt_dir, "/NONROOT")) 130 mntents[done-2].mnt_dir = nonroot_fs; 131 if (STREQ (mntents[done-2].mnt_dir, "/REMOTE")) 132 { 133 if (!remote_fs || !*remote_fs) 134 continue; 135 else 136 mntents[done-2].mnt_dir = remote_fs; 137 } 138 return &mntents[done-2]; 139 } 140 141 return NULL; 142} 143EOF 144 145# Then compile/link it: 146gcc_shared_ k.c k.so \ 147 || framework_failure_ 'failed to build shared library' 148 149# Test if LD_PRELOAD works: 150LD_PRELOAD=$LD_PRELOAD:./k.so df 151test -f x || skip_ "internal test failure: maybe LD_PRELOAD doesn't work?" 152 153# The fake mtab file should only contain entries 154# having the same device number; thus the output should 155# consist of a header and unique entries. 156LD_PRELOAD=$LD_PRELOAD:./k.so df -T >out || fail=1 157test $(wc -l <out) -eq $(expr 1 + $unique_entries) || { fail=1; cat out; } 158 159# With --total we should suppress the duplicate but separate remote file system 160LD_PRELOAD=$LD_PRELOAD:./k.so df --total >out || fail=1 161test "$CU_REMOTE_FS" && elide_remote=1 || elide_remote=0 162test $(wc -l <out) -eq $(expr 2 + $unique_entries - $elide_remote) || 163 { fail=1; cat out; } 164 165# Ensure we don't fail when unable to stat (currently) unavailable entries 166LD_PRELOAD=$LD_PRELOAD:./k.so CU_TEST_DUPE_INVALID=1 df -T >out || fail=1 167test $(wc -l <out) -eq $(expr 1 + $unique_entries) || { fail=1; cat out; } 168 169# df should also prefer "/fsname" over "fsname" 170if test "$unique_entries" = 2; then 171 test $(grep -c '/fsname' <out) -eq 1 || { fail=1; cat out; } 172 # ... and "/fsname" with '/' as Mounted on over '/.' 173 test $(grep -cF '/.' <out) -eq 0 || { fail=1; cat out; } 174fi 175 176# df should use the last seen devname (mnt_fsname) and devtype (mnt_type) 177test $(grep -c 'virtfs2.*t2' <out) -eq 1 || { fail=1; cat out; } 178 179# Ensure that filtering duplicates does not affect -a processing. 180LD_PRELOAD=$LD_PRELOAD:./k.so df -a >out || fail=1 181total_fs=6; test "$CU_REMOTE_FS" && total_fs=$(expr $total_fs + 3) 182test $(wc -l <out) -eq $total_fs || { fail=1; cat out; } 183# Ensure placeholder "-" values used for the eclipsed "virtfs" 184test $(grep -c 'virtfs *-' <out) -eq 1 || { fail=1; cat out; } 185 186# Ensure that filtering duplicates does not affect 187# argument processing (now without the fake getmntent()). 188df '.' '.' >out || fail=1 189test $(wc -l <out) -eq 3 || { fail=1; cat out; } 190 191Exit $fail 192