為 autofs 核心模組提供的各種裝置控制操作¶
問題¶
autofs 中存在活動重啟的問題(也就是說,當存在繁忙掛載時重啟 autofs)。
在正常操作期間,autofs 使用在正在管理的目錄上開啟的檔案描述符,以便能夠發出控制操作。使用檔案描述符使 ioctl 操作可以訪問儲存在超級塊中的 autofs 特定資訊。這些操作包括將 autofs 掛載設定為 catatonic、設定過期超時以及請求過期檢查。如下所述,某些型別的 autofs 觸發掛載最終可能會覆蓋 autofs 掛載本身,這會阻止我們在尚未開啟檔案描述符的情況下使用 open(2) 來獲取這些操作的檔案描述符。
目前,autofs 使用“umount -l”(惰性解除安裝)在重啟時清除活動掛載。雖然惰性解除安裝適用於大多數情況,但任何需要向上遍歷掛載樹以構造路徑的東西,例如 getcwd(2) 和 proc 檔案系統 /proc/<pid>/cwd,都將不再起作用,因為從中構造路徑的點已從掛載樹中分離。
autofs 的實際問題在於它無法重新連線到現有掛載。人們立刻想到只需新增重新掛載 autofs 檔案系統的功能即可解決此問題,但遺憾的是,這行不通。這是因為 autofs 直接掛載和巢狀掛載樹的“按需掛載和過期”的實現直接將檔案系統掛載在掛載觸發目錄 dentry 的頂部。
例如,有兩種型別的自動掛載對映:直接掛載(在核心模組原始碼中,您將看到第三種類型,稱為偏移量,它只是偽裝的直接掛載)和間接掛載。
這是一個包含直接和間接對映條目的主對映
/- /etc/auto.direct
/test /etc/auto.indirect
以及相應的對映檔案
/etc/auto.direct:
/automount/dparse/g6 budgie:/autofs/export1
/automount/dparse/g1 shark:/autofs/export1
and so on.
/etc/auto.indirect
g1 shark:/autofs/export1
g6 budgie:/autofs/export1
and so on.
對於上述間接對映,autofs 檔案系統掛載在 /test 上,並且每個子目錄鍵的掛載都由 inode 查詢操作觸發。例如,我們看到 shark:/autofs/export1 掛載在 /test/g1 上。
處理直接掛載的方式是在每個完整路徑上進行 autofs 掛載,例如 /automount/dparse/g1,並將其用作掛載觸發器。因此,當我們遍歷該路徑時,我們將 shark:/autofs/export1 掛載在“此掛載點之上”。由於這些始終是目錄,因此我們可以使用 follow_link inode 操作來觸發掛載。
但是,直接和間接對映中的每個條目都可以具有偏移量(使它們成為多掛載對映條目)。
例如,間接掛載對映條目也可以是
g1 \
/ shark:/autofs/export5/testing/test \
/s1 shark:/autofs/export/testing/test/s1 \
/s2 shark:/autofs/export5/testing/test/s2 \
/s1/ss1 shark:/autofs/export1 \
/s2/ss2 shark:/autofs/export2
類似地,直接掛載對映條目也可以是
/automount/dparse/g1 \
/ shark:/autofs/export5/testing/test \
/s1 shark:/autofs/export/testing/test/s1 \
/s2 shark:/autofs/export5/testing/test/s2 \
/s1/ss1 shark:/autofs/export2 \
/s2/ss2 shark:/autofs/export2
autofs 版本 4 的問題之一是,在掛載具有大量偏移量的條目時,可能存在巢狀,我們需要將所有偏移量作為一個單元進行掛載和解除安裝。對於對映條目中具有大量偏移量的人來說,這並不是什麼大問題。此機制用於眾所周知的“hosts”對映,並且我們已經看到(在 2.4 中)可用掛載數量已耗盡或可用特權埠數量已耗盡的情況。
在版本 5 中,我們僅在我們向下遍歷偏移量樹時進行掛載,同樣地,在使它們過期時也是如此,這解決了上述問題。該實現有一些更詳細的資訊,但為了解釋問題,不需要這些資訊。一個重要的細節是,這些偏移量使用與上述直接掛載相同的機制來實現,因此掛載點可以被掛載覆蓋。
當前 autofs 實現使用在掛載點上開啟的 ioctl 檔案描述符進行控制操作。描述符持有的引用在檢查以確定掛載是否正在使用中時進行計數,並且還用於訪問儲存在掛載超級塊中的 autofs 檔案系統資訊。因此,需要保留檔案控制代碼的使用。
解決方案¶
為了能夠重新啟動 autofs,將現有的直接掛載、間接掛載和偏移量掛載保留在原位,我們需要能夠獲取這些潛在覆蓋的 autofs 掛載點的檔案控制代碼。與其只實現一個隔離的操作,不如決定重新實現現有的 ioctl 介面並新增新操作來提供此功能。
此外,為了能夠重建具有繁忙掛載的掛載樹,需要提供觸發掛載的最後一個使用者的 uid 和 gid,因為這些可用作 autofs 對映中的宏替換變數。它們在掛載請求時記錄,並且已新增一個操作來檢索它們。
由於我們正在重新實現控制介面,因此已解決了現有介面的另外幾個問題。首先,當掛載或過期操作完成時,狀態會透過“傳送就緒”或“傳送失敗”操作從使用者空間返回到核心。ioctl 介面的“傳送失敗”操作只能傳送 ENOENT,因此重新實現允許使用者空間傳送實際狀態。使用者空間中的另一個昂貴操作(對於那些使用非常大的對映的人)是發現掛載是否存在。通常,這涉及掃描 /proc/mounts,並且由於它需要經常完成,因此當掛載表中有許多條目時,它可能會引入顯著的開銷。還添加了一個操作來查詢掛載點 dentry(覆蓋或未覆蓋)的掛載狀態。
當前的核心開發策略建議避免使用 ioctl 機制,而傾向於使用 Netlink 等系統。已嘗試使用此係統進行實現以評估其適用性,並且發現它在這種情況下是不夠的。通用 Netlink 系統用於此,因為原始 Netlink 會導致複雜性顯著增加。毫無疑問,通用 Netlink 系統是常見情況 ioctl 函式的優雅解決方案,但它不是一個完整的替代方案,可能是因為它的主要目的是作為訊息匯流排實現,而不是專門作為 ioctl 替代方案。雖然可以解決這個問題,但有一個問題導致了不使用它的決定。這是因為守護程式中的 autofs 過期變得過於複雜,因為列舉解除安裝候選項,幾乎只是為了“計數”呼叫過期 ioctl 的次數。這涉及掃描掛載表,這已被證明對於具有大型對映的使用者來說是一個很大的開銷。改進此問題的最佳方法是嘗試恢復到很久以前完成過期的方式。也就是說,當為掛載(檔案控制代碼)發出過期請求時,我們應該不斷回撥守護程式,直到我們無法解除安裝任何更多掛載,然後將適當的狀態返回給守護程式。目前,我們一次只使一個掛載過期。由於訊息匯流排架構的要求,通用 Netlink 實現將排除將來進行此開發的可能性。
autofs 雜項裝置掛載控制介面¶
控制介面是開啟一個裝置節點,通常是 /dev/autofs。
所有 ioctl 都使用一個公共結構來傳遞所需的引數資訊和返回操作結果
struct autofs_dev_ioctl {
__u32 ver_major;
__u32 ver_minor;
__u32 size; /* total size of data passed in
* including this struct */
__s32 ioctlfd; /* automount command fd */
/* Command parameters */
union {
struct args_protover protover;
struct args_protosubver protosubver;
struct args_openmount openmount;
struct args_ready ready;
struct args_fail fail;
struct args_setpipefd setpipefd;
struct args_timeout timeout;
struct args_requester requester;
struct args_expire expire;
struct args_askumount askumount;
struct args_ismountpoint ismountpoint;
};
char path[];
};
ioctlfd 欄位是 autofs 掛載點的掛載點檔案描述符。它由 open 呼叫返回,並由所有呼叫使用,但檢查給定路徑是否為掛載點的情況除外,在這種情況下,它可以選擇用於檢查與給定掛載點檔案描述符對應的特定掛載,以及當請求 autofs 檔案系統中目錄上最後一次成功掛載的 uid 和 gid 時。
union 用於傳遞引數和呼叫結果,如下所述。
path 欄位用於傳遞需要路徑的位置,size 欄位用於在轉換從使用者空間傳送的結構時考慮增加的結構長度。
可以透過使用 void 函式呼叫 init_autofs_dev_ioctl(struct autofs_dev_ioctl *) 在設定特定欄位之前初始化此結構。
所有 ioctl 都會將此結構從使用者空間複製到核心空間,如果 size 引數小於結構大小本身,則返回 -EINVAL;如果核心記憶體分配失敗,則返回 -ENOMEM;如果複製本身失敗,則返回 -EFAULT。其他檢查包括編譯後的使用者空間版本與模組版本的版本檢查,如果不匹配,則返回 -EINVAL。如果 size 欄位大於結構大小,則假定存在路徑,並檢查以確保它以“/”開頭並以 NULL 結尾,否則返回 -EINVAL。在這些檢查之後,對於除 AUTOFS_DEV_IOCTL_VERSION_CMD、AUTOFS_DEV_IOCTL_OPENMOUNT_CMD 和 AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD 之外的所有 ioctl 命令,都會驗證 ioctlfd,如果它不是有效的描述符或不對應於 autofs 掛載點,則返回 -EBADF、-ENOTTY 或 -EINVAL(不是 autofs 描述符)錯誤。
ioctl¶
可以使用 autofs 5.0.4 及更高版本中的檔案 lib/dev-ioctl-lib.c(可從 kernel.org 的 /pub/linux/daemons/autofs/v5 目錄下載的發行 tar 包中獲取)來檢視使用此介面的實現的示例。
此介面實現的裝置節點 ioctl 操作為
AUTOFS_DEV_IOCTL_VERSION¶
獲取 autofs 裝置 ioctl 核心模組實現的主版本號和次版本號。它需要一個初始化的 struct autofs_dev_ioctl 作為輸入引數,並在傳入的結構中設定版本資訊。如果檢測到版本不匹配,則返回 0 表示成功,或返回錯誤 -EINVAL。
AUTOFS_DEV_IOCTL_PROTOVER_CMD 和 AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD¶
獲取載入模組理解的 autofs 協議版本的主版本號和次版本號。此呼叫需要一個初始化的 struct autofs_dev_ioctl,其中 ioctlfd 欄位設定為有效的 autofs 掛載點描述符,並在 struct args_protover 的 version 欄位或 struct args_protosubver 的 sub_version 欄位中設定請求的版本號。如果驗證失敗,這些命令返回 0 表示成功,或返回一個負錯誤程式碼。
AUTOFS_DEV_IOCTL_OPENMOUNT 和 AUTOFS_DEV_IOCTL_CLOSEMOUNT¶
獲取和釋放 autofs 管理的掛載點路徑的檔案描述符。open 呼叫需要一個初始化的 struct autofs_dev_ioctl,其中 path 欄位已設定,size 欄位已適當調整,並且 struct args_openmount 的 devid 欄位已設定為 autofs 掛載的裝置號。裝置號可以從 /proc/mounts 中顯示的掛載選項中獲取。close 呼叫需要一個初始化的 struct autofs_dev_ioctl,其中 ioctlfd 欄位設定為從 open 呼叫獲取的描述符。也可以使用 close(2) 完成檔案描述符的釋放,因此任何開啟的描述符也會在程序退出時關閉。close 呼叫包含在已實現的操作中,主要是為了完整性並提供一致的使用者空間實現。
AUTOFS_DEV_IOCTL_READY_CMD 和 AUTOFS_DEV_IOCTL_FAIL_CMD¶
將掛載和過期結果狀態從使用者空間返回到核心。這兩個呼叫都需要一個初始化的 struct autofs_dev_ioctl,其中 ioctlfd 欄位設定為從 open 呼叫獲取的描述符,並且 struct args_ready 或 struct args_fail 的 token 欄位設定為等待佇列令牌號,該令牌號由使用者空間在前述掛載或過期請求中接收。struct args_fail 的 status 欄位設定為操作的 errno。成功時,它設定為 0。
AUTOFS_DEV_IOCTL_SETPIPEFD_CMD¶
設定用於核心與守護程式通訊的管道檔案描述符。通常,這是在掛載時使用選項設定的,但是當重新連線到現有掛載時,我們需要使用它來告訴 autofs 掛載新的核心管道描述符。為了保護掛載免受錯誤設定管道描述符的影響,我們還需要 autofs 掛載處於 catatonic 狀態(請參閱下一個呼叫)。
該呼叫需要一個初始化的 struct autofs_dev_ioctl,其中 ioctlfd 欄位設定為從 open 呼叫獲取的描述符,並且 struct args_setpipefd 的 pipefd 欄位設定為管道的描述符。成功後,該呼叫還會將用於標識控制程序(例如,擁有的 automount(8) 守護程式)的程序組 ID 設定為呼叫者的程序組。
AUTOFS_DEV_IOCTL_CATATONIC_CMD¶
使 autofs 掛載點處於 catatonic 狀態。autofs 掛載將不再發出掛載請求,核心通訊管道描述符將被釋放,並且佇列中剩餘的任何等待都將被釋放。
該呼叫需要一個初始化的 struct autofs_dev_ioctl,其中 ioctlfd 欄位設定為從 open 呼叫獲取的描述符。
AUTOFS_DEV_IOCTL_TIMEOUT_CMD¶
設定 autofs 掛載點中掛載的過期超時。
該呼叫需要一個初始化的 struct autofs_dev_ioctl,其中 ioctlfd 欄位設定為從 open 呼叫獲取的描述符。
AUTOFS_DEV_IOCTL_REQUESTER_CMD¶
返回最後一個成功觸發給定路徑 dentry 上掛載的程序的 uid 和 gid。
該呼叫需要一個初始化的 struct autofs_dev_ioctl,其中 path 欄位設定為有問題的掛載點,並且 size 欄位已適當調整。返回時,struct args_requester 的 uid 欄位包含 uid,gid 欄位包含 gid。
當重建具有活動掛載的 autofs 掛載樹時,我們需要重新連線到可能已使用原始程序 uid 和 gid(或它們的字串變體)的掛載,以便在對映條目中進行掛載查詢。此呼叫提供了獲取此 uid 和 gid 的能力,以便使用者空間可以將它們用於掛載對映查詢。
AUTOFS_DEV_IOCTL_EXPIRE_CMD¶
向核心發出 autofs 掛載的過期請求。通常,呼叫此 ioctl 直到找不到其他過期候選項。
該呼叫需要一個初始化的 struct autofs_dev_ioctl,其中 ioctlfd 欄位設定為從 open 呼叫獲取的描述符。此外,可以透過將 struct args_expire 的 how 欄位分別設定為 AUTOFS_EXP_IMMEDIATE 或 AUTOFS_EXP_FORCED 來請求獨立於掛載超時的立即過期,以及獨立於掛載是否繁忙的強制過期。如果找不到過期候選項,則 ioctl 返回 -1,errno 設定為 EAGAIN。
此呼叫使核心模組檢查與給定 ioctlfd 對應的掛載,以查詢可以過期的掛載,向守護程式發出過期請求,並等待完成。
AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD¶
檢查 autofs 掛載點是否正在使用中。
該呼叫需要一個初始化的 struct autofs_dev_ioctl,其中 ioctlfd 欄位設定為從 open 呼叫獲取的描述符,並在 struct args_askumount 的 may_umount 欄位中返回結果,1 表示繁忙,0 表示否則。
AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD¶
檢查給定路徑是否為掛載點。
該呼叫需要一個初始化的 struct autofs_dev_ioctl。有兩種可能的變體。兩者都使用設定為要檢查的掛載點路徑的 path 欄位,並且 size 欄位已適當調整。一個使用 ioctlfd 欄位來標識要檢查的特定掛載點,而另一個變體使用路徑,並可以選擇使用 struct args_ismountpoint 的 in.type 欄位設定為 autofs 掛載型別。如果這是一個掛載點,則該呼叫返回 1,並將 out.devid 欄位設定為掛載的裝置號,out.magic 欄位設定為相關的超級塊魔術數(如下所述),如果不是掛載點,則返回 0。在這兩種情況下,裝置號(由 new_encode_dev() 返回)都在 out.devid 欄位中返回。
如果提供了檔案描述符,我們正在尋找一個特定的掛載,不一定位於已掛載堆疊的頂部。在這種情況下,描述符對應的路徑被認為是掛載點,如果它本身是一個掛載點或包含一個掛載,例如沒有根掛載的多掛載。在這種情況下,如果描述符對應於一個掛載點,則返回 1,如果存在覆蓋掛載,則還返回覆蓋掛載的超級魔術數,如果不是掛載點,則返回 0。
如果提供了路徑(並且 ioctlfd 欄位設定為 -1),則查詢該路徑並檢查它是否為掛載的根。如果還給出了型別,我們正在尋找特定的 autofs 掛載,如果未找到匹配項,則返回失敗。如果找到的路徑是掛載的根,則返回 1,以及掛載的超級魔術數,否則返回 0。