はじめに
職場で、mount で表示すると出てくるのに、dfだと表示されないものがあるというので、ちょっと勉強してみたのでそのメモです。
手がかり
職場での出来事なので、あまり詳細について書くことができません。
NFSサーバの3つのディレクトリをそれぞれ、別のマウントポイントでマウントしているそうです。そのうちの1つだけが表示されない状況。
そうすると、NFSサーバ側の問題は考えにくいと思います。
複数のマシンからマウントしているみたいですが、特定の1つだけdfで表示できないっぽい感じ。(このあたり詳細を聞いていないのでなんとも。)
mountコマンドで表示すると出てくるのに、dfだと出てこない。
あと、マウントポイントの先のデータも参照等できている。(なのでマウント自体はできていると思われる。)
仮説: dfを疑おう
dfとmountでおそらく情報源となっているものが違っていて、それが何らかの原因で差異が生じているのでは、と仮説を立ててみた。
mount コマンドの情報源
マニュアル見ると書いてあった。
mount および umount プログラムによって現在マウントされているファイルシステムの一覧は /etc/mtab ファイル中に記述されている。 mount が
引き数なしで実行された場合には、 このリストが表示される。
man 8 mount より
自分の手元のPC(Linux Mint 19.3)だと /etc/mtab は/proc/self/mounts へのシンボリックリンクでした。職場のはRedHatEnterpriseLinuxの7系だった気がするので、また違うかもしれない。
df コマンドの情報源
df のマニュアル見たけど、特に書いていない。うーん、しょうがない。ソースコードを読もう。でもバージョンわかんない。まぁいいや。とりあえず割と新しいやつを読んでみておこう、というわけで GNU coreutilsの8.32を読むことに。
特に手がかりとかないので、まずはmainから。
int
main (int argc, char **argv)
{
struct stat *stats IF_LINT ( = 0);
initialize_main (&argc, &argv);
set_program_name (argv[0]);
setlocale (LC_ALL, "");
bindtextdomain (PACKAGE, LOCALEDIR);
textdomain (PACKAGE);
atexit (close_stdout);
fs_select_list = NULL;
fs_exclude_list = NULL;
show_all_fs = false;
show_listed_fs = false;
human_output_opts = -1;
print_type = false;
file_systems_processed = false;
exit_status = EXIT_SUCCESS;
print_grand_total = false;
grand_fsu.fsu_blocksize = 1;
/* If true, use the POSIX output format. */
bool posix_format = false;
const char *msg_mut_excl = _("options %s and %s are mutually exclusive");
while (true)
{
int oi = -1;
int c = getopt_long (argc, argv, "aB:iF:hHklmPTt:vx:", long_options, &oi);
最初の方のやつは、GNUのコマンドでよく出てくるやつですね。(前にls を読んだときにもあった。)その後の変数は名前から推測すると、コマンドのオプション値を保存するためのものかなぁと思います。while文はコマンドオプションの処理だと思います。
というわけでこの辺をずーと飛ばしてみると、1780行目にマウントしているもののリストを取り出しているっぽいところを発見。
mount_list =
read_file_system_list ((fs_select_list != NULL
|| fs_exclude_list != NULL
|| print_type
|| field_data[FSTYPE_FIELD].used
|| show_local_fs));
read_file_system_list は lib/mountlist.c にありました。
struct mount_entry *
read_file_system_list (bool need_fs_type)
{
struct mount_entry *mount_list;
(省略)
#ifdef MOUNTED_GETMNTENT1 /* glibc, HP-UX, IRIX, Cygwin, Android,
also (obsolete) 4.3BSD, SunOS */
{
FILE *fp;
#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";
fp = fopen (mountinfo, "r");
if (fp != NULL)
{
(省略)
} else /* fallback to /proc/self/mounts (/etc/mtab). */
#endif /* linux || ANDROID */
{
struct mntent *mnt;
char const *table = MOUNTED;
fp = setmntent (table, "r");
(省略)
どうやら、最初に/proc/self/mountinfo を読もうとしていて、それができないときに/etc/mtab にフォールダウンするみたいですね。
ちなみに、MOUNTEDは、マクロで_PATH_MOUNTEDと定義されていました。_PATH_MOUNTEDは、glibcのsysdeps/unix/sysv/linux/paths.hによると/etc/mtab なので、上記の記載とも一致しています。
結論: まだわからないです
出勤したら、また調べてみようと思います。
とりあえず、/proc/self/mountinfo と/etc/mtab の差異を調べて見る感じですね。でも、自分がログインして調べるわけじゃないのが、ちょっと面倒です。
サーバの運用保守を委託してやっているので、あんまり自分がログインして、とかすると責任分解が、とか言われてしまうのです。とほほ。
2020/07/06 追記
原因は、このChangeLogにある内容でした。
df: prioritize mounts nearer the device root
In the presence of bind mounts of a device, the 4th “mount root” field
from /proc/self/mountinfo is now considered, so as to prefer mount
points closer to the root of the device. Note on older systems with
an /etc/mtab file, the source device was listed as the originating
directory, and so this was not an issue.
Details at http://pad.lv/1432871
coreutils/ChangeLog
ソースコードの続きの箇所で、filter_mount_list() という関数があって、そこで、bind mount の場合に重複して表示させないように処理しつつ、NFSの場合は意図したものとして、ちゃんと表示するように書いてありました。
bool target_nearer_root = strlen (seen_dev->me->me_mountdir)
> strlen (me->me_mountdir);
/* With bind mounts, prefer items nearer the root of the source */bool source_below_root = seen_dev->me->me_mntroot != NULL
&& me->me_mntroot != NULL
&& (strlen (seen_dev->me->me_mntroot)
< strlen (me->me_mntroot));
if (! print_grand_total
&& me->me_remote && seen_dev->me->me_remote
&& ! STREQ (seen_dev->me->me_devname, me->me_devname))
{
/* Don't discard remote entries with different locations,
as these are more likely to be explicitly mounted.
However avoid this when producing a total to give
a more accurate value in that case. */
}
無駄といえば無駄でしたが、いろいろ勉強になったのでラッキーでした。