logger 参数列表过长_[源码级解析] 巧妙解决并深度分析Linux下rm命令提示参数列表过长的问题...
本文原載于未命名小站,由作者本人同步至知乎,轉載請注明原作者博客地址或本鏈接,謝謝!
0x01
最初我以為是rm命令對文件數量存在限制,但當我嘗試ls ./*、touch ./*等命令都遇到這一問題之后,我開始將注意力放在Bash本身上。也許是通配符的限制。
突然,我想起來rm命令支持管道送入參數,也許可以通過這樣的辦法變相完成任務。于是我在另一個目錄做了個測試:
$ touch 123-1 $ touch 123-2 $ echo "123-1 123-2" | xargs rm這兩個文件果然消失。
于是我嘗試使用find將目錄下面所有文件列出:
find . -name "*" | more # 使用more避免350多萬個文件把終端擠崩潰 find . -name "*" | wc -l # 大致了解文件個數輸出的文件個數與我通過ls -l命令輸出的個數基本一致,果然輸出了所有文件。接下來要做的就是將這些文件送到rm中。
0x02
但事實并非如此簡單,當我執行以下命令,以為可以一口氣順利刪除所有文件的時候,我傻眼了:
$ find . -name "*" | xargs rm rm: log: No such file or directory rm: 20190601-110204.log: No such file or directory ... # 所有待刪除文件均發生報錯我重新觀察文件名,發現文件名格式均為log yyyymmdd-hhmmss.log,眾所周知Bash靠空格分割參數,文件名被傳入rm的時候照著空格被截斷,成為了兩個文件名,難怪刪除失敗!
0x03
吸取教訓,我使用了一個新的參數:
find . -name "*" -print0 | xargs -0 rm注意這一參數里的-print0與-0,這是為了區分空格與分界符,加入參數后此前用于分隔參數的空格(0x0a)則會變成NUL(0x00),這一參數的效果可以通過16進制查看器體現:
$ ls 123 321 123 322 $ find . -name "12*" > 1.log $ find . -name "12*" -print0 > 2.log $ hexdump -C 1.log 00000000 2e 2f 31 32 33 20 33 32 31 0a 2e 2f 31 32 33 20 |./123 321../123 | 00000010 33 32 32 0a |322.| 00000014 $ hexdump -C 2.log 00000000 2e 2f 31 32 33 20 33 32 31 00 2e 2f 31 32 33 20 |./123 321../123 | 00000010 33 32 32 00 |322.| 00000014可以發現在兩個不同輸出模式下,分隔符不一樣。默認的分隔符與空格一致,即0x0a,但當開啟-print0模式后,分隔符變成了0x00,配合管道接收端的-0參數將NUL字符正確解析成參數定界符,則可以完成帶空格文件名的正常解析。
解決了這一問題,我們再次執行,問題隨即解決。
0x04
過了兩天,又有一臺服務器需要刪除大量文件,且領導要求只刪除文件不刪除里面的目錄,我一看,又是400多萬個文件。但在之前的折騰過程中,我早有準備。
find命令默認開啟遞歸模式,如果只刪除文件不刪除目錄,只需要配置遞歸深度為1即可:
find . -maxdepth 1 -name "*" -print0 | xargs -0 rm執行命令后再執行ls -l,發現問題果然解決,所有目錄完好無損。
0x05
快速解決完問題后,我一看離下班還有好一陣子,便開始琢磨Bash內通配符的長度限制到底從哪來。
我首先找了另一個有大量文件的目錄開始實驗,換用zsh進行ls ./*操作,得到的確是一樣結果。看來該長度限制與Bash無關。
我突然想起來曾經看過的一個安全類視頻:Youtube - Bash injection without letters or numbers - 33c3ctf hohoho (misc 350) - LiveOverFlow,其中解釋了通配符(Wildcard)的原理。
當我們輸入*的時候,Shell所做的是列舉出滿足通配符規則的所有文件,并以空格分割,然后送進Shell。舉例而言,如果你有一個全是txt文件的目錄,你直接執行*,就會發現以下錯誤:
$ touch abc.txt $ touch bbc.txt $ * bash: command not found: abc.txt $ echo * abc.txt bbc.txt相信看到這里,大家都明白通配符的行為以及為什么上述示例會報錯,在上述示例中,Shell將abc.txt看做命令,而將bbc.txt看做參數。這也說明了通配符的行為:將滿足條件的文件輸出為文本(并默認輸出到Shell)。
0x06
當我們繼續向下挖掘,我們會想到Shell執行命令的本質:exec()類系統調用。這一限制如果并非來自于Shell(因為find . -name "*"并不會報錯),那么就一定來自于系統調用。而一個Shell命令被執行的第一站則是exec()及其六個實際調用:execl(),execle(),execlp(),execv(),execvp(),execve()。
于是我們使用strace工具來檢查所有系統調用:
$ ls 1234.txt 123.txt $ strace ls * execve("/usr/bin/ls", ["ls", "1234.txt", "123.txt"], [/* 28 vars */]) = 0 ...看到這里,相信讀者已經心里有數了,我們的命令與參數都被作為execve()函數的第二個參數,以數組形式被傳入。考慮到數組默認存儲在棧中,該限制是否來自于Shell對棧空間的限制呢?
我查閱了Linux的源碼,在fs/exec.c:478中找到了我要的內容:源碼
static int prepare_arg_pages(struct linux_binprm *bprm,struct user_arg_ptr argv, struct user_arg_ptr envp) {unsigned long limit, ptr_size;bprm->argc = count(argv, MAX_ARG_STRINGS);if (bprm->argc < 0)return bprm->argc;bprm->envc = count(envp, MAX_ARG_STRINGS);if (bprm->envc < 0)return bprm->envc;/** Limit to 1/4 of the max stack size or 3/4 of _STK_LIM* (whichever is smaller) for the argv+env strings.* This ensures that:* - the remaining binfmt code will not run out of stack space,* - the program will have a reasonable amount of stack left* to work from.*/limit = _STK_LIM / 4 * 3;limit = min(limit, bprm->rlim_stack.rlim_cur / 4);/** We've historically supported up to 32 pages (ARG_MAX)* of argument strings even with small stacks*/limit = max_t(unsigned long, limit, ARG_MAX);/** We must account for the size of all the argv and envp pointers to* the argv and envp strings, since they will also take up space in* the stack. They aren't stored until much later when we can't* signal to the parent that the child has run out of stack space.* Instead, calculate it here so it's possible to fail gracefully.*/ptr_size = (bprm->argc + bprm->envc) * sizeof(void *);if (limit <= ptr_size)return -E2BIG;limit -= ptr_size;bprm->argmin = bprm->p - limit;return 0; }從完整的注釋中我們可以得知,限制最大參數長度的參數叫做ARG_MAX,而且其大小為棧的1/4(可能是為了保證參數以外還有多的空間可以存儲其他數據)。當然,如果你是個考古愛好者,你會發現在2.6版本內核(低于2.6.22)的include/linux/limits.h文件中,ARG_MAX是一個寫死的常量 :考古鏈接。
關于ARG_MAX我們可以通過Linux下的getconf命令來獲取,這是一個獲取Linux下所有全局變量/常量的命令:
$ getconf ARG_MAX 2097152我們再查詢當前配置的棧大小:
$ ulimit -s 8192ARG_MAX參數的單位是Byte,ulimit -s命令的單位是MB,可以看到當前最大參數數量的確是棧空間的1/4。那如果我們把棧空間增大呢?
$ ulimit -s 81920 $ ulimit -s 81920 $ getconf ARG_MAX 20971520可以看到,允許的最大參數數量立馬隨著棧空間增大而同步增大。這個時候我們再來刪除之前那個大目錄,就不會出現『參數列表過長』的錯誤提示了。
實際上這一限制在大多數現代操作系統中均存在(例如MacOS、Windows等),可參考此處獲得更多信息:ARG_MAX, maximum length of arguments for a new process
總結
以上是生活随笔為你收集整理的logger 参数列表过长_[源码级解析] 巧妙解决并深度分析Linux下rm命令提示参数列表过长的问题...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: import oracle utilit
- 下一篇: 开发转测试没人要_新人如何快速的进入融入