問題的提出
在處理檔案系統路徑的時候,我們一般會先開闢一塊記憶體區,用來接收路徑、或者拼接好路徑傳遞給系統呼叫。這是因為路徑在各個系統上都有最大長度限制,在 Windows 上這個值是 MAX_PATH,一般不能超過 260;在 Linux 上這個值是 PATH_MAX,一般不能超過 4096 (或者通過 pathconf (_PC_PATH_MAX, ...) 來獲取,但是一般也是 4096),就像下面這段典型的程式碼:
1 int main () 2 { 3 #ifdef WIN32 4 char buf[MAX_PATH + 1] = { 0 }; 5 if (GetModuleFileNameA(NULL, buf, MAX_PATH) == 0) 6 { 7 printf("get current module path failed, errno %d", GetLastError()); 8 return -1; 9 } 10 #else 11 char buf[PATH_MAX + 1] = { 0 }; 12 if (readlink("/proc/self/exe", buf, PATH_MAX) < 0) 13 { 14 printf("read exe path failed, errno %d", errno); 15 return -1; 16 } 17 #endif 18 19 printf("current executable file path: %s\n", buf); 20 return 0; 21 }
它用來獲取當前可執行檔案的完整路徑。在 Windows 與 Linux 上分別呼叫了 GetModuleFileName 與 readlink 系統呼叫,且事先在棧上分配了接收完整路徑的記憶體區 buf。最後將成功獲取的路徑列印到控制檯。在兩種系統上,輸出分別如下:
Windows
current executable file path: E:\code\apue\02.chapter\Release\path_max.exe
Linux
current executable file path: /home/yunhai/code/apue/02.chapter/path_max
其它與路徑相關的呼叫也是類似的。那麼問題來了,這個最大路徑長度是為了方便程式編寫 (不然需要動態分配記憶體,且需要兩次呼叫,其中一次用於獲取最終的路徑長度),還是說底層的檔案系統就只能支援這麼長的路徑呢?
問題的驗證
為了弄清楚這個問題,我專門寫了一個測試程式:
1 #ifdef WIN32 2 # ifndef _WIN32_WINNT // 指定要求的最低平臺是 Windows Vista。 3 # define _WIN32_WINNT 0x0600 // 將此值更改為相應的值,以適用於 Windows 的其他版本。 4 # endif 5 #include <windows.h> 6 #include <direct.h> 7 #else 8 #include <limits.h> 9 #include <unistd.h> 10 #include <fcntl.h> 11 #include <errno.h> 12 #include <sys/stat.h> 13 #endif 14 #include <string.h> 15 #include <stdio.h> 16 #include <stdlib.h> 17 #include <time.h> 18 19 void get_random_name (char *name, int len, int level) 20 { 21 int i, n; 22 sprintf (name, "%d", level); 23 n = strlen (name); 24 for (i=n; i<len; ++ i) 25 name[i] = rand () % 26 + 'a'; 26 27 name [len] = 0; 28 } 29 30 int main (int argc, char *argv[]) 31 { 32 int dir_len = 0, file_len = 0; 33 char *name = 0; 34 int ret = 0, level = 0; 35 #ifdef WIN32 36 int name_max = MAX_PATH; 37 int path_max = MAX_PATH; 38 HANDLE fd = 0; 39 #else 40 char const* path = "/"; 41 int name_max = pathconf (path, _PC_NAME_MAX); 42 int path_max = pathconf (path, _PC_PATH_MAX); 43 #endif 44 45 printf ("NAME_MAX = %d, PATH_MAX = %d\n", 46 name_max, path_max); 47 48 // add 1 (/) to 10 to be conveniently to compute how low the path is with level. 49 dir_len = 9; 50 file_len = 100; 51 52 srand (time(0)); 53 name = (char *) calloc (1, (dir_len > file_len ? dir_len : file_len) + 1); 54 if (name == 0) 55 return -1; 56 57 58 do 59 { 60 get_random_name (name, dir_len, ++level); 61 #ifdef WIN32 62 //ret = _mkdir (name); 63 //if (ret == -1) 64 ret = CreateDirectory(name, NULL); 65 if (!ret) 66 { 67 printf ("CreateDirectory %s failed, errno = %d\n", name, GetLastError ()); 68 break; 69 } 70 #else 71 ret = mkdir (name, 0777); 72 if (ret == -1) 73 { 74 printf ("mkdir %s failed, errno = %d\n", name, errno); 75 break; 76 } 77 #endif 78 79 printf ("mkdir %s\n", name); 80 #ifdef WIN32 81 //ret = _chdir (name); 82 //if (ret == -1) 83 ret = SetCurrentDirectory(name); 84 if (!ret) 85 { 86 printf ("SetCurrentDirectory %s failed, errno = %d\n", name, GetLastError ()); 87 break; 88 } 89 #else 90 ret = chdir (name); 91 if (ret == -1) 92 { 93 printf ("chdir %s failed, errno = %d\n", name, errno); 94 break; 95 } 96 #endif 97 98 //printf ("change to that dir\n"); 99 get_random_name (name, file_len, level); 100 #ifdef WIN32 101 fd = CreateFile (name, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, 0, 0); 102 if (fd == INVALID_HANDLE_VALUE) 103 printf ("open %s failed, errno = %d\n", name, GetLastError ()); 104 else 105 { 106 printf ("open %s OK.\n", name); 107 CloseHandle (fd); 108 } 109 #else 110 int fd = open (name, O_RDWR | O_CREAT, 0644); 111 if (fd == -1) 112 printf ("open %s failed, errno = %d\n", name, errno); 113 else 114 { 115 printf ("open %s OK.\n", name); 116 close (fd); 117 } 118 #endif 119 } while (1); 120 121 free (name); 122 return 0; 123 }
這個小程式很簡單,每次建立一個目錄,切換進去,建立一個檔案。這樣周而復始,直到出錯退出。目錄名長度固定為 9,這樣加上目錄分隔符(/ 或 \)就正好湊成 10,方便之後根據目錄深度計算路徑總長度;檔名長度固定為 100,也是為了方便計算路徑總長度。每個名字都是由 26 個字母隨機組成的,同時將目錄深度 (level)記錄在名字開始。例如第 10 級目錄可能長這樣:“10jmvrfqv”,前面的數字 10 明確的標識了目錄層級,同理,檔名前面也是層級數字,也是為了方便計算。好了,我們分別在兩個系統上跑一下這個測試程式。
1. Windows 上的結果 ( Win10 x64)
NAME_MAX = 260, PATH_MAX = 260 mkdir 1tdobxciu open 1gybhgvdtngozbkzvcuvpnitcpntxgtjhasljfycfowuuqmcvvitsthzdxpckttobaqtccxeabfmnvhoeknyjifabnynkrjwzhiq OK. mkdir 2wafsehgp open 2ssywxcafcvpuppgxjcpctjkleftvyhwpuzydlcaaxkbsgumljccmokznqpkvliklndweyegqxvmrmtcrfwzqyllezvneztvqwma OK. mkdir 3vmkjwgrp open 3bclyodmcwjgzmokchhkrmharodswgmpbusxozgqogaguvppohpthetfgqxrmihdjjmqsxvdzgondfsirxfmqbmgmexdnbdjyrqr OK. mkdir 4xmvhqrfh open 4hclzbqzoniuuolhumysibxonqutbpqgvroamdwdorhypsfkrkyskqykdukfrngtfipgjidvazgsvdfejofbjqqwpthkfxzgwubw OK. mkdir 5rwoxiiwh open 5sdeemddmvkhszsubkojdemrconicercdpdcmsmitbbuxowvsqbfghagpwkgmdrytzfnafoqvwsktiwdkjskfukxtxjeknctdjcm OK. mkdir 6fnmlhdor open 6vjrqxecbutxcospyzihwjabulwzbvbwimubvvchdxgyhqfjebjnpbbhvtwrkjlowmzbpoqkshwjbzoqysxfthztefvzwgarrdon OK. mkdir 7sbvhcsxw open 7ddmdylgkhghdeefydnjfsmiyxwutewbmujsppdezpoamtwodvqvkyaeswywbfnvjctofiaftmkiwapbjawuhjvuqsmefjpfgbew OK. mkdir 8dwhpsgaj open 8xepfibfufsaefmghnjukuddbqhyijunkazscfjxkznnylylpcaiauwyrzcfixbjfguvimzwrabcnvxomkaelrwtgnhsjxpiwmii OK. mkdir 9wmpqcalh open 9ldugmypucabxtjnpxqdvinxtevztnspuakrwwhebpshzsgbkedvmxbibkwyaoxctfelwjsglvdwjfjffuleijofuoinnrdcdymf OK. mkdir 10vlwydie open 10jihhaxrcpsviddxserikiiyrtbiwdumzdsyhkkodreimjxivkpnnxxtgpuathmqatlajmfueupwsdwjhyferopxiqggkywccvz OK. mkdir 11awvhauw open 11ypsctazqpuebetmzzgrzasmjmzppymtdabgcajahhwuwilzxlgvpupuksrjtmigjogjyqxhczuunlknxupfixnyxlyjsdhgwuf OK. mkdir 12pdznsnw open 12easlcjvcdudtktfkseedbtlbvnjhjtlchtjbbshmjbdsnpifidqjwsttalzwlyjezixtghwortsusrifigbxhpznmiigtvviym OK. mkdir 13pvvjtcb open 13lbpsmpbwyzzcnjlixarmvbxpqeketgycbduhzlhhakxykncmxwhmgqrwigcywiehemmhlinrhxnxbktentpvnlazrzgniwgcnf OK. mkdir 14jrhanvs open 14nlvfzgxyfzekthrzidzoeugldefwnxlelbntfetdnxvxkcupkpnzxhytidycrstkchdojzrsdjxclkvfgvmmsenysyyinkylsd OK. mkdir 15yilwbim open 15dhjbeoeybggfrjpstbyobosrmigtfhmpbajgwhjgczgklphxweefjoliwbuabyxyfsnzjzpiaxdagfbkujrqistslqkffqmilx OK. mkdir 16owgedmp open 16cqauvpdzsghgmzdbdxaqdclrvuyophifemvsoygwuhbtlkkmwmwxekwcxgmauagexxtyirthfgjbztmutljstjiwcbwmntkfri failed, errno = 3 mkdir 17xljxtht open 17umkwtdbxiayekrljssfnxgvcmytthwevlmrxptivqmvwrkpqkvxoircuwadhkyidoaydlvnnafejpssqdewcymluzmrwrrkwmn failed, errno = 3 mkdir 18aywtihk open 18ynydjmncmakzoezcupoeqfiahsunxhqvczbuapntehglrrhubpbydnnpnbpjdschmrkzkkpvgfajemsfsvofsjoclijforjkex failed, errno = 3 mkdir 19qsvtxwm open 19qpfdrjgnxthljturhvuymuoqctsmixxrircoievqpgrnxpzufozwvpsjocdxfhjodpfzljbgegutfsquaehjoaswqhwtsqbgmh failed, errno = 3 mkdir 20vzvuovy open 20zqqxsfnxrccsubxwevfffunrxfugkovltzidalrmppjjyrbnohxgmwnmwcseijuhzabngyfodgodnblmvhmvnmkvqfijucfqfz failed, errno = 3 mkdir 21idgwupz open 21vlkqahypwulaaykizzvqviqztswdwikvloswluydfyqmgjcaixqqrilsrjireprfrvbgfvspulfmnuksbvsnfkdncybuhxddtw failed, errno = 3 mkdir 22plbyhbe open 22rbekcixmxcohnvqlyhlppmgqtpsgsjwxhuwlmhnzspkbvdrnzechjbjsglzajiadiqguutgfjfkaqdtfqeevpcrsejiiotytse failed, errno = 3 mkdir 23vggwvra open 23tlbobatwbdpaxouojluctxqyddllqedqbeotpkkxgddristpprablooxlomgvneixubhxhemtlvipqwzzfgqcxjtdltytpialw failed, errno = 3 mkdir 24dkpkfup open 24fujnicyzpudgnsuzcqkkgihsousejefcuepnkkxdpdxbxxfryeglrmikgxxckazfokmdrgttnqxavphpoekagfbenjyuxpfntu failed, errno = 3 CreateDirectory 25aajjgcq failed, errno = 206
我是在 G:\ 根目錄執行這個程式的,跑到第 16 級目錄時,CreateFile 失敗返回了,錯誤碼為 3 (ERROR_PATH_NOT_FOUND:系統找不到指定的路徑),對於檔案建立失敗,這裡忽略,所以程式繼續執行;跑到第 25 級目錄時,CreateDirectory 也失敗返回了,錯誤碼為 206 (ERROR_FILENAME_EXCED_RANGE:檔名或副檔名太長),因為不能再繼續了,所以整個程式終止。看了一下,到 16 級目錄時整個路徑是這樣:
總長度達到 162,此時如果想再建立檔案的話,總長度會升至 162 + 1 + 100 = 263 > MAX_PATH,所以接下來的檔案建立失敗了。然後再看下到 24 級目錄時路徑的樣子:
總長度達到 243,如果再加上一個目錄的長度的話,應該是253 < MAX_PATH,按理說應該還可以容納一個目錄的建立,但是我實地在這個目錄下面嘗試建立目錄,得到了這樣的錯誤:
如果是建立檔案的話,會發現輸入一定長度的檔名之後,就輸入不了了:
這個長度目前是 16 (算上字尾 .txt 4個字元),加上之前目錄的長度 243,總長度為 243 + 1 + 16 = 260,正好等於 MAX_PATH。可見,對於 Windows 而言,這個 MAX_PATH 就是底層檔案系統的限制,再怎麼玩也玩不出這個槓槓。嘗試切換 CreateDirectory / SetCurrentDirectory 實現為 _mkdir / _chdir,看看有什麼不同 :
line 62-65
ret = _chdir (name); if (ret == -1) //ret = SetCurrentDirectory(name); //if (!ret)
line 81-84
ret = _mkdir (name); if (ret == -1) //ret = CreateDirectory(name, NULL); //if (!ret)
重跑上面的例子,會得到完全一樣的結果。
2. Linux 上的結果 (neokylin x64)
一開始我是在 CentOS i686 環境上跑的,沒想到一陣跑之後再回來看,虛擬機器桌面居然重置了,所有 shell 都退出了,沒有截到任何輸出,無奈只好採用另一個平臺了。在這個中標麒麟的環境跑完了全程:
$ ./path_max NAME_MAX = 255, PATH_MAX = 4096 mkdir 1rkxtumwh open 1tovmschzmqujtonfqbqyfomyykrtwqcrezfxbmypesyygmfxpvxvjmvkwnfudhnhgsghgfwkzxkgjpfamcxwotimgniluvucpbl OK. mkdir 2vijihgsn open 2rkuswxpuniccqrkbliwpzzaxhkfqqzgklackzrgpclrscduqnsfnrikasrqkswuexwqwqwlshekkigaxyhkrpwrjniuieqodnea OK. mkdir 3fanxiuju open 3cqwzqdmivizeyjygopureytlajkkdtehlciegumddnhdxgjnxfedfaofjysmtytfbbjjyxmdmtgjbrwywdddfrkopcclbxqcabn OK. mkdir 4yazbmsjy open 4wawuybydgrowjqyutxmxynkypjcbelzaowwoxwsgqiczabtuaftyuezjpbmvomxekutjsnpixuhxxctxhnwdtvmiybemnbrzxmk OK. mkdir 5sbcaywkx open 5torqwgobzlqjlrnygrrhqdrigvletvbmjufhcvldjbnuscuavlhmqawwwhcpegeqalzfgkirnvmfaghvuojkpghlnlcsrgkurjz OK. mkdir 6aujthffp open 6howeinoseubrjntbgclavxzxrjraqwrzmqdudrphnsaxhtaovootlqqebheteysqoxntpccewcbevcstqimbacfbklwqjqixovs OK. mkdir 7fzujwwnc open 7upunhebiffpircfhmxsllpespacnxpptglgnrjxyoohgqopclhqzxuroutdtkumshufydexusgcjwtnhbdiaxaquvvnircaywgy OK. mkdir 8cmxwffao open 8btdlwhtygtqaplqxcszcofbsuyqzeqphmssizmjgfziwlyvpsvsjcvbwtsxzkmhwhzhinqovrztezpusmodqjepeyoejdnhkoos OK. mkdir 9cgjxyiqe open 9jhydvmgovlfaltguwvegjubndkmbufhdoghmupasahuoackzxohijiwmukpopwueedqbsstvzpjbrtcokkyvuujrfahwwbcdeue OK. mkdir 10xmzunod open 10rfytviftdcqmtxmatlewqkqujfvdsjgjqffnpkgtoyhhvwkrhqnxaftkmqphbysrdxeukmpylxigtuxckmbmuwyimopnmkgrjk OK. …… mkdir 1341932sc open 1341932rlcbgduqybifdacrgopzdnulnwxjtrlldpmltibrjlzolbhuqytvnpgbmgmhzzscpfniprbbearsezmuxhrnwaqkgcsfb OK. mkdir 1341933kk open 1341933qrzyhsckxcbpgcdcckupgufrchjjivvbnvbwndgmgjdomgroqndzjiqnrbwbwuclpejejrtrbwgndzdvoguxqmkhpikoc OK. mkdir 1341934nz open 1341934ttlyecrwdpcssdvqrdmovzaeqipeneggxrgbvxzzpbthgrzxwmnsnnwdylilpotohasczrdovzybqxzmjmewcdaaokngy OK. mkdir 1341935gu open 1341935hgmjgenwzmuaesbsbozaqccsqmfwmnttufcclrykfvnjnocqebswduovgvusjnlduqhghhspcfypuciafaxivlgdhcwqr OK. mkdir 1341936jv open 1341936lzerjlkaqryhndpoisltnwzqfdoxuysfzyzkklkccljqozgyrrrgpsyvvnupnpxonwbzjlblyldomjndcglszlpwyjony OK. mkdir 1341937le open 1341937ojfnsspftcjhouwtxderepgcqrqfsdjgmqvejmkcovlcpivonbfttnvjhmqzpahcqeibruffrrijbfxojfhcuelbqdcgd OK. mkdir 1341938lk open 1341938wquzhrfoiyytzerpnyyrscewvhadnmplejkncrblpaeqgxhtxhkqkoohxokkczvikgvnzxarxghderybyktkajrxybkca OK. mkdir 1341939fl open 1341939lnganfaecgnhmehqftjpvthvtkfvlkiyypynuarzggitnsjsluhhpqckbjgotootfogcoxduglptfylrvuaklevondeit OK. mkdir 1341940sc open 1341940bgiduigqotghzgssbmuozylpnqtvjlzktipnqxggrmnsshkwvekvevmtngqwtqjnaacsyiyruljpstlnzxkfuxyhfodyg OK. mkdir 1341941ol open 1341941gpqypywgvhpkbkxrlubqoaovfeaelprrghqvimbdwspzemqpgshwsvrabrgmixgohyjpmmuigjikxybfsibnfunjltvws OK. mkdir 1341942dk failed, errno = 28
中間輸出比較多,就省略了,只顯示前後十幾行內容。我是在 /home/vmware/code/apue/02.chapter 目錄下執行這個程式的,跑到 1341942 級目錄時,mkdir 失敗返回了,錯誤碼為 28 (ENOSPC:No space left on device)。總的目錄路徑長度達到 34 + 1341941 * 10 = 13419444,最深層的一個檔案總路徑長度達到 34 + 1341941 * 10 + 100 = 13419544,都遠遠大於 PATH_MAX (4096)了。此時我在執行程式的目錄新建檔案或目錄,都會失敗。
$ mkdir abc mkdir: 無法建立目錄"abc": 裝置上沒有空間 $ touch abc touch: 無法建立"abc": 裝置上沒有空間
所以可以肯定的一點是,Linux 上的 PATH_MAX 並不是底層檔案系統對路徑最大長度的限制,只是一種便於程式編寫的常量。那路徑的最大長度到底由什麼決定呢?看了上面 ENOSPC 的錯誤碼,我第一反應是硬碟空間滿了,然而在檢視了磁碟剩餘空間後,我否決了這個可能性。下面是我在執行測試程式前後分別記錄的 df 輸出。
可以看到所在的 /home 掛載點使用比例激增 (8%~22%),但是也沒有達到 100%,所以磁碟空間還是比較充足的,很可能只是 inode 用光了(?)。不管怎樣,本質上都是一種資源限制,這使得程式設計師可以在 Linux 上建立比較長的檔案路徑,比 Windows 提供了更大的靈活性。但是這樣長的路徑並不被所有程式所識別,例如,我在剛才建立測試目錄的地方,遞迴列出所有檔案,輸出如下:
$ ls -R .: 1rkxtumwh Makefile name_max.c path_max path_max.o path_max.vcxproj apue.o name_max name_max.o path_max.c path_max.sln ./1rkxtumwh: 1tovmschzmqujtonfqbqyfomyykrtwqcrezfxbmypesyygmfxpvxvjmvkwnfudhnhgsghgfwkzxkgjpfamcxwotimgniluvucpbl 2vijihgsn ./1rkxtumwh/2vijihgsn: 2rkuswxpuniccqrkbliwpzzaxhkfqqzgklackzrgpclrscduqnsfnrikasrqkswuexwqwqwlshekkigaxyhkrpwrjniuieqodnea 3fanxiuju ./1rkxtumwh/2vijihgsn/3fanxiuju: 3cqwzqdmivizeyjygopureytlajkkdtehlciegumddnhdxgjnxfedfaofjysmtytfbbjjyxmdmtgjbrwywdddfrkopcclbxqcabn 4yazbmsjy ./1rkxtumwh/2vijihgsn/3fanxiuju/4yazbmsjy: 4wawuybydgrowjqyutxmxynkypjcbelzaowwoxwsgqiczabtuaftyuezjpbmvomxekutjsnpixuhxxctxhnwdtvmiybemnbrzxmk 5sbcaywkx ./1rkxtumwh/2vijihgsn/3fanxiuju/4yazbmsjy/5sbcaywkx: 5torqwgobzlqjlrnygrrhqdrigvletvbmjufhcvldjbnuscuavlhmqawwwhcpegeqalzfgkirnvmfaghvuojkpghlnlcsrgkurjz 6aujthffp ./1rkxtumwh/2vijihgsn/3fanxiuju/4yazbmsjy/5sbcaywkx/6aujthffp: 6howeinoseubrjntbgclavxzxrjraqwrzmqdudrphnsaxhtaovootlqqebheteysqoxntpccewcbevcstqimbacfbklwqjqixovs 7fzujwwnc ./1rkxtumwh/2vijihgsn/3fanxiuju/4yazbmsjy/5sbcaywkx/6aujthffp/7fzujwwnc: 7upunhebiffpircfhmxsllpespacnxpptglgnrjxyoohgqopclhqzxuroutdtkumshufydexusgcjwtnhbdiaxaquvvnircaywgy 8cmxwffao ./1rkxtumwh/2vijihgsn/3fanxiuju/4yazbmsjy/5sbcaywkx/6aujthffp/7fzujwwnc/8cmxwffao: 8btdlwhtygtqaplqxcszcofbsuyqzeqphmssizmjgfziwlyvpsvsjcvbwtsxzkmhwhzhinqovrztezpusmodqjepeyoejdnhkoos 9cgjxyiqe ./1rkxtumwh/2vijihgsn/3fanxiuju/4yazbmsjy/5sbcaywkx/6aujthffp/7fzujwwnc/8cmxwffao/9cgjxyiqe: 10xmzunod 9jhydvmgovlfaltguwvegjubndkmbufhdoghmupasahuoackzxohijiwmukpopwueedqbsstvzpjbrtcokkyvuujrfahwwbcdeue ./1rkxtumwh/2vijihgsn/3fanxiuju/4yazbmsjy/5sbcaywkx/6aujthffp/7fzujwwnc/8cmxwffao/9cgjxyiqe/10xmzunod: 10rfytviftdcqmtxmatlewqkqujfvdsjgjqffnpkgtoyhhvwkrhqnxaftkmqphbysrdxeukmpylxigtuxckmbmuwyimopnmkgrjk …… 410dntuzt ls: 無法開啟目錄./1rkxtumwh/2vijihgsn/3fanxiuju/4yazbmsjy/5sbcaywkx/6aujthffp/7fzujwwnc/8cmxwffao/9cgjx yiqe/10xmzunod/11ltybuja/12pddukks/13pgjmtme/14yxyuhvt/15qgddfps/16ipghtsx/17hrlvjqw/18mlwamyj/19ycqq oum/20gdekplr/21mgkqtef/22uxjelyo/23yuogjxv/24gdyuetr/25ekyseyq/26mohxeho/27yhfufeo/28emftmwp/29detfm nz/30ulhqorq/31lzvmcau/32vrkdiho/33pwrixvt/34ysjlzxm/35anbimen/36aazpcto/37dpzcvhb/38vbxsrwc/39urooly a/40gabvtkg/41fswpnkc/42eyozjbb/43mbsphto/44szukcad/45lobskag/46njddwrk/47qmyaqip/48qywwpge/49jymbads /50cevctwp/51dbrvvrg/52alaqbak/53wdxytcx/54iajcocc/55yulgnls/56xdvlohr/57rsyzzme/58avlsjct/59kzwpnmn/ 60zuuyott/61cmyywqh/62fyuzvce/63djljats/64aopcwyi/65rxefdvy/66egqsclw/67jxyausw/68atlilhv/69xrxqsel/7 0isglkct/71fwkjdms/72mhygulk/73nlnwbrx/74sroynrd/75dimglds/76ulettre/77cvdbchx/78imrnssw/79uqfezsr/80 lkhipxs/81rbdpqqz/82lvyzxqt/83sgxgrxx/84tsgcvwa/85jaamdba/86zoneybo/87glbanpe/88tkzaefg/89rrwsack/90u owxuoq/91rabkpyi/92dcnxiyz/93lacaahs/94frjwezd/95yhtpnte/96fblotmh/97mibhekj/98rzonuec/99topwrdn/100d blgkn/101drisrx/102aqjjso/103uefcom/104yrflxq/105hyfxyh/106tlpbnl/107ndiswq/108uvgksb/109ozcwup/110qx pxlo/111gttfdp/112nepxhw/113xwflah/114yibgjy/115dffvxj/116ukiosf/117fpvyvt/118kdreee/119ejgwiz/120lpo qvc/121gihnlr/122umltry/123ajaaeq/124unorwj/125iuipco/126uwghec/127lzrzvo/128zytdwi/129xvkgeh/130mgfx pr/131kekuik/132fjlbpy/133wogrgw/134eotsoo/135fquige/136smcyju/137obzwup/138fewkay/139dgizof/140gqrvp w/141lwxiji/142gprlwz/143geyxsn/144otqwya/145ooyfvk/146ucigye/147xstuji/148ziprio/149ufrzci/150lmgpli /151kczfla/152svbdit/153fzlyns/154yirhkv/155xbdixx/156mogbae/157goxflw/158fixfsg/159ncihug/160bnrvsj/ 161ynpfeh/162cmanrm/163utjekr/164qcvzim/165ypxvkh/166ggnvdw/167nszvsr/168uyuayf/169fatffy/170znjsdj/1 71jqobhe/172syewyj/173fbdzgu/174bufnki/175qasxqn/176rigmyh/177zwoexf/178bdrxbg/179ggdmzx/180ljyrtm/18 1qbywid/182wsjhtx/183moknmr/184haaeaa/185jdpxza/186eerxfa/187kpfjiq/188wrdbbh/189uuhvct/190ofpwct/191 qnmtmy/192gqyowy/193eckugg/194oonhah/195dbjuxd/196qzbvuh/197kcqziu/198akbhoa/199hkszfc/200aqieum/201a hkuqb/202hkikbi/203gywiex/204ibfmca/205vgczdp/206pchnch/207tfkxlo/208orztrq/209foperp/210umtatf/211rf qqzo/212xuspzm/213bsqdet/214kbvvmg/215qsbepc/216qqqvea/217mtyasr/218eunbgv/219stppxz/220ncqzlb/221qgl ips/222naqylv/223ymrfpk/224vavwhr/225dznual/226iuraac/227ztobic/228jynnfd/229iatquu/230vuokzi/231ykpc bl/232wqldxe/233qeebyv/234nvspvv/235iqxfye/236nwnztb/237qjtgnn/238qyzyas/239lhcqon/240piorsv/241upvvm j/242sjgemx/243ndojai/244whhuvw/245hmojut/246kwaiwh/247ohckmb/248ucmdrh/249eqzimi/250uymmqd/251cnglst /252iysywl/253jridil/254nmoqzh/255ygguhk/256nwuiap/257zvtolc/258urycli/259kpiuab/260jempzc/261jyqcbe/ 262sulagk/263mvljld/264ciiawa/265vjxora/266rxfgkg/267jwvpoy/268mpmgja/269xpxycf/270fjxobt/271zahncm/2 72soljdu/273jcdwrq/274wvrusy/275noyexu/276oksjfe/277wzfwvr/278tcsfue/279uhzgjs/280lfpypw/281cyjibv/28 2jwnlgu/283nnktxs/284udmuma/285xzbwnj/286jemqma/287xnedax/288dcvfle/289ddnbqe/290jheimg/291rscgbr/292 zlyhgw/293jbmjzx/294qocgsi/295mbyvyj/296ntwpkk/297jhofrm/298ibirpc/299kquens/300mbjnmo/301najyhm/302g yerut/303blongi/304tafajx/305suvetf/306faeoiu/307dunnih/308ifvtsq/309vulqxe/310ilvtjv/311gzknfk/312ju xeni/313bpaezt/314dcqgbb/315rqnuic/316ejhbrb/317wfzvzo/318hfndtr/319zgsznw/320dqahuh/321mdnyne/322xiu lms/323lplrvo/324qtvodk/325tiigxk/326kljzob/327lxxopm/328qwjlms/329jwcbic/330nowwgr/331jqqbce/332qael pn/333vyyvlr/334wvchpm/335afdbpb/336vsrvym/337ktyfii/338gasxaz/339gjjnco/340yqdxcc/341eysxtl/342ipsoc o/343xrjucz/344aoetoq/345rtnyzv/346ptrthp/347gnxqkl/348vfpcrp/349jmoobg/350glqvzi/351vkznzy/352cmjseg /353zgxeyh/354vfkgth/355fnhgjo/356cyouso/357hruehw/358jadbhv/359hklsla/360hwsife/361ragklc/362xydtpr/ 363tjrhwj/364gpsojy/365sphazl/366fgwang/367tovqhr/368ybyzbe/369wasspn/370jjkehg/371fvmqie/372bdigtd/3 73afocqc/374agluqq/375jlrfsu/376gtbemx/377lqbrlg/378xmxxvu/379pwznpd/380gdryol/381cgzdfe/382egzxfd/38 3ciccll/384mpoifm/385ygdqiy/386ahilzb/387bhmyia/388cmfhah/389kuxsqg/390winlgk/391jnjlim/392vurqut/393 yehkub/394vkgknd/395pxnmgr/396bsnizx/397bzbbbs/398kdfjjs/399ugopik/400klxzic/401kaqhcd/402iyrruo/403d tncvg/404feosyw/405cbohnn/406hfirse/407tqbohz/408udrjbo/409yhmpsz/410dntuzt: 檔名過長
中間輸出比較多,就省略了。跑到 410 級目錄時,ls 報錯了,上一級目錄路徑的總長度達到 34 + 409 * 10 = 4124,其實已經比 PATH_MAX 大了,不過還是因為緩衝區不足而中斷退出了。再嘗試查詢檔案,輸出如下:
$ find . . ./name_max.o ./path_max.c ./Makefile ./name_max ./path_max ./path_max.o ./name_max.c ./1rkxtumwh ./1rkxtumwh/1tovmschzmqujtonfqbqyfomyykrtwqcrezfxbmypesyygmfxpvxvjmvkwnfudhnhgsghgfwkzxkgjpfamcxwotimgniluvucpbl ./1rkxtumwh/2vijihgsn ./1rkxtumwh/2vijihgsn/3fanxiuju ./1rkxtumwh/2vijihgsn/3fanxiuju/3cqwzqdmivizeyjygopureytlajkkdtehlciegumddnhdxgjnxfedfaofjysmtytfbbjjyxmdmtgjbrwywdddfrkopcclbxqcabn ./1rkxtumwh/2vijihgsn/3fanxiuju/4yazbmsjy ./1rkxtumwh/2vijihgsn/3fanxiuju/4yazbmsjy/5sbcaywkx ./1rkxtumwh/2vijihgsn/3fanxiuju/4yazbmsjy/5sbcaywkx/6aujthffp ./1rkxtumwh/2vijihgsn/3fanxiuju/4yazbmsjy/5sbcaywkx/6aujthffp/6howeinoseubrjntbgclavxzxrjraqwrzmqdudrphnsaxhtaovootlqqebheteysqoxntpccewcbevcstqimbacfbklwqjqixovs ./1rkxtumwh/2vijihgsn/3fanxiuju/4yazbmsjy/5sbcaywkx/6aujthffp/7fzujwwnc ./1rkxtumwh/2vijihgsn/3fanxiuju/4yazbmsjy/5sbcaywkx/6aujthffp/7fzujwwnc/7upunhebiffpircfhmxsllpespacnxpptglgnrjxyoohgqopclhqzxuroutdtkumshufydexusgcjwtnhbdiaxaquvvnircaywgy ./1rkxtumwh/2vijihgsn/3fanxiuju/4yazbmsjy/5sbcaywkx/6aujthffp/7fzujwwnc/8cmxwffao ./1rkxtumwh/2vijihgsn/3fanxiuju/4yazbmsjy/5sbcaywkx/6aujthffp/7fzujwwnc/8cmxwffao/8btdlwhtygtqaplqxcszcofbsuyqzeqphmssizmjgfziwlyvpsvsjcvbwtsxzkmhwhzhinqovrztezpusmodqjepeyoejdnhkoos ./1rkxtumwh/2vijihgsn/3fanxiuju/4yazbmsjy/5sbcaywkx/6aujthffp/7fzujwwnc/8cmxwffao/9cgjxyiqe ./1rkxtumwh/2vijihgsn/3fanxiuju/4yazbmsjy/5sbcaywkx/6aujthffp/7fzujwwnc/8cmxwffao/9cgjxyiqe/10xmzunod ./1rkxtumwh/2vijihgsn/3fanxiuju/4yazbmsjy/5sbcaywkx/6aujthffp/7fzujwwnc/8cmxwffao/9cgjxyiqe/10xmzunod/10rfytviftdcqmtxmatlewqkqujfvdsjgjqffnpkgtoyhhvwkrhqnxaftkmqphbysrdxeukmpylxigtuxckmbmuwyimopnmkgrjk ……
最後有沒有完成我沒有得到結論,因為整個虛擬機器歷時 N 天這個命令還沒跑完 (N > 10),執行中的 find 截圖為證:
我是按記憶體佔用從高到低排序的,可以看到經過 N 天的執行 find 命令的記憶體佔用已經超過了整個圖形介面(Xorg),另外與 find 命令關聯的終端 (mate-terminal) 記憶體、CPU也在高位執行。不過至少可以說明 find 並沒有使用 PATH_MAX 來簡單限制路徑長度,可能是通過動態分配記憶體來實現對長路徑支援的。另外個人比較好奇這個命令的控制程式碼開支,於是看了下 lsof 的輸出:
$ lsof -p `pidof find` COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME find 113339 vmware cwd DIR 8,5 4096 2095005 /home/vmware/code/apue/02.chapter find 113339 vmware rtd DIR 8,3 4096 2 / find 113339 vmware txt REG 8,3 203296 1180356 /usr/bin/find find 113339 vmware mem REG 8,3 106374736 1187939 /usr/lib/locale/locale-archive find 113339 vmware mem REG 8,3 141880 1191334 /usr/lib64/libpthread-2.20.so find 113339 vmware mem REG 8,3 19512 1190784 /usr/lib64/libdl-2.20.so find 113339 vmware mem REG 8,3 447240 1191294 /usr/lib64/libpcre.so.1.2.3 find 113339 vmware mem REG 8,3 2082456 1190695 /usr/lib64/libc-2.20.so find 113339 vmware mem REG 8,3 1167000 1191118 /usr/lib64/libm-2.20.so find 113339 vmware mem REG 8,3 154784 1191415 /usr/lib64/libselinux.so.1 find 113339 vmware mem REG 8,3 163184 1190512 /usr/lib64/ld-2.20.so find 113339 vmware mem REG 8,3 26254 1448972 /usr/lib64/gconv/gconv-modules.cache find 113339 vmware mem REG 8,3 30239 3015834 /usr/share/locale/zh_CN/LC_MESSAGES/findutils.mo find 113339 vmware 0u CHR 136,0 0t0 3 /dev/pts/0 find 113339 vmware 1u CHR 136,0 0t0 3 /dev/pts/0 find 113339 vmware 2u CHR 136,0 0t0 3 /dev/pts/0 find 113339 vmware 3r DIR 8,5 4096 2095005 /home/vmware/code/apue/02.chapter find 113339 vmware 4r unknown /proc/113339/fd/4 (readlink: File name too long) find 113339 vmware 6r unknown /proc/113339/fd/6 (readlink: File name too long) find 113339 vmware 7r unknown /proc/113339/fd/7 (readlink: File name too long) find 113339 vmware 9r unknown /proc/113339/fd/9 (readlink: File name too long) find 113339 vmware 10r unknown /proc/113339/fd/10 (readlink: File name too long) find 113339 vmware 11r unknown /proc/113339/fd/11 (readlink: File name too long)
出人意料的是並沒有很多開啟中的檔案控制程式碼。按我的理解,每遍歷一層目錄應該有一個目錄檔案的控制程式碼被開啟,但是現在看沒有,是因為我造的例子比較特殊,每個目錄下只有一個子目錄?還是 find 另闢蹊徑不用佔用許多控制程式碼?暫時不得而知。總的來講,find 的表現要好於 ls,頓時對這個神奇的命令產生了敬仰。最後插一句題外話,從上面的輸出我們可以看到另一個現象,就是因為路徑太長了,readlink 返回了錯誤,可見對長路徑,很多系統 api 也是不支援的。最後你可以使用 rm -rf xxxx 來嘗試清理這些目錄與檔案:
不出所料 rm 佔用了很多的記憶體和 CPU,不過好歹可以工作 (而且耗時沒有 find 那麼誇張,也就十幾分鍾),所以我的虛擬機器可以恢復到測試前的工況。下面是 lsof 針對 rm 的一些輸出
$ lsof -p `pidof rm`
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
rm 70867 vmware cwd DIR 8,5 4096 2095005 /home/vmware/code/apue/02.chapter
rm 70867 vmware rtd DIR 8,3 4096 2 /
rm 70867 vmware txt REG 8,3 62872 1181241 /usr/bin/rm
rm 70867 vmware mem REG 8,3 106374736 1187939 /usr/lib/locale/locale-archive
rm 70867 vmware mem REG 8,3 2082456 1190695 /usr/lib64/libc-2.20.so
rm 70867 vmware mem REG 8,3 163184 1190512 /usr/lib64/ld-2.20.so
rm 70867 vmware 0u CHR 136,0 0t0 3 /dev/pts/0
rm 70867 vmware 1u CHR 136,0 0t0 3 /dev/pts/0
rm 70867 vmware 2u CHR 136,0 0t0 3 /dev/pts/0
rm 70867 vmware 3u unknown /proc/70867/fd/3 (readlink: File name too long)
rm 70867 vmware 4u unknown /proc/70867/fd/4 (readlink: No such file or directory)
rm 70867 vmware 5u unknown /proc/70867/fd/5 (readlink: File name too long)
rm 70867 vmware 6u unknown /proc/70867/fd/6 (readlink: File name too long)
rm 70867 vmware 7u unknown /proc/70867/fd/7 (readlink: File name too long)
rm 70867 vmware 8u unknown /proc/70867/fd/8 (readlink: File name too long)
和 find 一樣,它沒有多少開啟中的檔案控制程式碼(否則開啟檔案控制程式碼數早超限了)。另外從 lsof 的輸出看,有一些節點已經被 rm 刪除了,所以 readlink 報錯。
結論
對超長的檔案路徑來說,不同的 Linux 命令的支援程度也是不同的,有的支援,有的不支援。而且考慮到傳遞給程式的命令列總長度也是有限制的,除了一些支援遞迴目錄處理的命令外,普通的命令是不會支援這麼長的路徑的。另一方面,從程式設計師的角度考慮,對於大路徑的支援也是比較麻煩的,首先是系統支不支援輸入、輸出超長路徑;如果支援,輸入的場景還比較好辦,輸出的場景就比較麻煩了。最簡單的辦法是自己定義一個大於 PATH_MAX 值的常量並使用它分配記憶體,但是這樣也存在問題,一方面日常處理比較浪費記憶體;另一方面如果路徑超過你自己定義的這個值,還是會出現接收截斷的問題。而且程式設計師沒有辦法去預知要得到的路徑長度,從而提前動態分配記憶體。
總結一下,這個最大路徑限制並不是系統不能支援超長的路徑,而且作為一種系統與應用之間的一個約定,有了這個約定之後,程式對路徑的處理將得到簡化。況且我們沒有對超長路徑的硬需求,絕大部分 Windows 應用在 260 個字元路徑的限制下就跑得很好,Linux 4096 的限制就更不用說了。
這個測試程式的一個額外用處就是耗盡所在分割槽的 inode(?),並觀察系統在這種極限情況下的一些表現,例如,當我試圖擷取螢幕時不能成功,得到下面的提示框:
當你需要驗證程式在這種極限狀態下的行為時,這個程式不失為一種有用工具。