logger 参数列表过长_[源码级解析] 巧妙解决并深度分析Linux下rm命令提示参数列表过长的问题...
本文原載于未命名小站,由作者本人同步至知乎,轉(zhuǎn)載請(qǐng)注明原作者博客地址或本鏈接,謝謝!
0x01
最初我以為是rm命令對(duì)文件數(shù)量存在限制,但當(dāng)我嘗試ls ./*、touch ./*等命令都遇到這一問(wèn)題之后,我開(kāi)始將注意力放在Bash本身上。也許是通配符的限制。
突然,我想起來(lái)rm命令支持管道送入?yún)?shù),也許可以通過(guò)這樣的辦法變相完成任務(wù)。于是我在另一個(gè)目錄做了個(gè)測(cè)試:
$ touch 123-1 $ touch 123-2 $ echo "123-1 123-2" | xargs rm這兩個(gè)文件果然消失。
于是我嘗試使用find將目錄下面所有文件列出:
find . -name "*" | more # 使用more避免350多萬(wàn)個(gè)文件把終端擠崩潰 find . -name "*" | wc -l # 大致了解文件個(gè)數(shù)輸出的文件個(gè)數(shù)與我通過(guò)ls -l命令輸出的個(gè)數(shù)基本一致,果然輸出了所有文件。接下來(lái)要做的就是將這些文件送到rm中。
0x02
但事實(shí)并非如此簡(jiǎn)單,當(dāng)我執(zhí)行以下命令,以為可以一口氣順利刪除所有文件的時(shí)候,我傻眼了:
$ find . -name "*" | xargs rm rm: log: No such file or directory rm: 20190601-110204.log: No such file or directory ... # 所有待刪除文件均發(fā)生報(bào)錯(cuò)我重新觀察文件名,發(fā)現(xiàn)文件名格式均為log yyyymmdd-hhmmss.log,眾所周知Bash靠空格分割參數(shù),文件名被傳入rm的時(shí)候照著空格被截?cái)?#xff0c;成為了兩個(gè)文件名,難怪刪除失敗!
0x03
吸取教訓(xùn),我使用了一個(gè)新的參數(shù):
find . -name "*" -print0 | xargs -0 rm注意這一參數(shù)里的-print0與-0,這是為了區(qū)分空格與分界符,加入?yún)?shù)后此前用于分隔參數(shù)的空格(0x0a)則會(huì)變成NUL(0x00),這一參數(shù)的效果可以通過(guò)16進(jìn)制查看器體現(xiàn):
$ 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可以發(fā)現(xiàn)在兩個(gè)不同輸出模式下,分隔符不一樣。默認(rèn)的分隔符與空格一致,即0x0a,但當(dāng)開(kāi)啟-print0模式后,分隔符變成了0x00,配合管道接收端的-0參數(shù)將NUL字符正確解析成參數(shù)定界符,則可以完成帶空格文件名的正常解析。
解決了這一問(wèn)題,我們?cè)俅螆?zhí)行,問(wèn)題隨即解決。
0x04
過(guò)了兩天,又有一臺(tái)服務(wù)器需要?jiǎng)h除大量文件,且領(lǐng)導(dǎo)要求只刪除文件不刪除里面的目錄,我一看,又是400多萬(wàn)個(gè)文件。但在之前的折騰過(guò)程中,我早有準(zhǔn)備。
find命令默認(rèn)開(kāi)啟遞歸模式,如果只刪除文件不刪除目錄,只需要配置遞歸深度為1即可:
find . -maxdepth 1 -name "*" -print0 | xargs -0 rm執(zhí)行命令后再執(zhí)行l(wèi)s -l,發(fā)現(xiàn)問(wèn)題果然解決,所有目錄完好無(wú)損。
0x05
快速解決完問(wèn)題后,我一看離下班還有好一陣子,便開(kāi)始琢磨Bash內(nèi)通配符的長(zhǎng)度限制到底從哪來(lái)。
我首先找了另一個(gè)有大量文件的目錄開(kāi)始實(shí)驗(yàn),換用zsh進(jìn)行l(wèi)s ./*操作,得到的確是一樣結(jié)果。看來(lái)該長(zhǎng)度限制與Bash無(wú)關(guān)。
我突然想起來(lái)曾經(jīng)看過(guò)的一個(gè)安全類視頻:Youtube - Bash injection without letters or numbers - 33c3ctf hohoho (misc 350) - LiveOverFlow,其中解釋了通配符(Wildcard)的原理。
當(dāng)我們輸入*的時(shí)候,Shell所做的是列舉出滿足通配符規(guī)則的所有文件,并以空格分割,然后送進(jìn)Shell。舉例而言,如果你有一個(gè)全是txt文件的目錄,你直接執(zhí)行*,就會(huì)發(fā)現(xiàn)以下錯(cuò)誤:
$ touch abc.txt $ touch bbc.txt $ * bash: command not found: abc.txt $ echo * abc.txt bbc.txt相信看到這里,大家都明白通配符的行為以及為什么上述示例會(huì)報(bào)錯(cuò),在上述示例中,Shell將abc.txt看做命令,而將bbc.txt看做參數(shù)。這也說(shuō)明了通配符的行為:將滿足條件的文件輸出為文本(并默認(rèn)輸出到Shell)。
0x06
當(dāng)我們繼續(xù)向下挖掘,我們會(huì)想到Shell執(zhí)行命令的本質(zhì):exec()類系統(tǒng)調(diào)用。這一限制如果并非來(lái)自于Shell(因?yàn)閒ind . -name "*"并不會(huì)報(bào)錯(cuò)),那么就一定來(lái)自于系統(tǒng)調(diào)用。而一個(gè)Shell命令被執(zhí)行的第一站則是exec()及其六個(gè)實(shí)際調(diào)用:execl(),execle(),execlp(),execv(),execvp(),execve()。
于是我們使用strace工具來(lái)檢查所有系統(tǒng)調(diào)用:
$ ls 1234.txt 123.txt $ strace ls * execve("/usr/bin/ls", ["ls", "1234.txt", "123.txt"], [/* 28 vars */]) = 0 ...看到這里,相信讀者已經(jīng)心里有數(shù)了,我們的命令與參數(shù)都被作為execve()函數(shù)的第二個(gè)參數(shù),以數(shù)組形式被傳入。考慮到數(shù)組默認(rèn)存儲(chǔ)在棧中,該限制是否來(lái)自于Shell對(duì)棧空間的限制呢?
我查閱了Linux的源碼,在fs/exec.c:478中找到了我要的內(nèi)容:源碼
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; }從完整的注釋中我們可以得知,限制最大參數(shù)長(zhǎng)度的參數(shù)叫做ARG_MAX,而且其大小為棧的1/4(可能是為了保證參數(shù)以外還有多的空間可以存儲(chǔ)其他數(shù)據(jù))。當(dāng)然,如果你是個(gè)考古愛(ài)好者,你會(huì)發(fā)現(xiàn)在2.6版本內(nèi)核(低于2.6.22)的include/linux/limits.h文件中,ARG_MAX是一個(gè)寫死的常量 :考古鏈接。
關(guān)于ARG_MAX我們可以通過(guò)Linux下的getconf命令來(lái)獲取,這是一個(gè)獲取Linux下所有全局變量/常量的命令:
$ getconf ARG_MAX 2097152我們?cè)俨樵儺?dāng)前配置的棧大小:
$ ulimit -s 8192ARG_MAX參數(shù)的單位是Byte,ulimit -s命令的單位是MB,可以看到當(dāng)前最大參數(shù)數(shù)量的確是棧空間的1/4。那如果我們把棧空間增大呢?
$ ulimit -s 81920 $ ulimit -s 81920 $ getconf ARG_MAX 20971520可以看到,允許的最大參數(shù)數(shù)量立馬隨著棧空間增大而同步增大。這個(gè)時(shí)候我們?cè)賮?lái)刪除之前那個(gè)大目錄,就不會(huì)出現(xiàn)『參數(shù)列表過(guò)長(zhǎng)』的錯(cuò)誤提示了。
實(shí)際上這一限制在大多數(shù)現(xiàn)代操作系統(tǒng)中均存在(例如MacOS、Windows等),可參考此處獲得更多信息:ARG_MAX, maximum length of arguments for a new process
總結(jié)
以上是生活随笔為你收集整理的logger 参数列表过长_[源码级解析] 巧妙解决并深度分析Linux下rm命令提示参数列表过长的问题...的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: import oracle utilit
- 下一篇: 开发转测试没人要_新人如何快速的进入融入