Linux

為什麼不能在單獨的程序中更改目錄?

  • August 15, 2021

為什麼我們不能創建一個改變目前工作目錄的程序。就像cd命令一樣?

想想你在問什麼。你有一個 shell,你想寫一個外部程序來改變 shell 的 cwd。這甚至有意義嗎?對我來說,聽起來你並沒有完全理解 shell 只是一個接受字元串並產生一些子程序的程序(這些子程序自然繼承其父程序 shell 的 cwd)的方式。

我懷疑您可能會很笨拙地做到這一點,也許是通過使用 ptrace 將命令注入到 shell 程序中。您可以編寫自己的 shell,為“child wd”保留一個不同的設置(主要變化是在它派生一個孩子之後,它需要記住 chdir())。您可以編寫一個核心模組,讓您強制更改任意程序的 cwd,儘管我希望這會破壞事情而不是有用。

如果這種事情有任何意義,您可以(具有適當的權限)執行“ln -fs /somewhere /proc/$pid/cwd”。當我以 root 身份嘗試時,我得到“ln: cwd/: cannot overwrite directory”——來自 strace,看起來它正在檢查並註意到目標已經存在。symlink() 在這種情況下似乎會失敗,因此 symlink 命令可能必須首先刪除現有連結,我認為核心不會讓這種情況發生。

您不能更改子程序中的目前工作目錄,因為……您實際上可以做得很好

clone(2)用於創建程序(fork(2)並實現CLONE_FS

#define _GNU_SOURCE
#include <sched.h>
#include <unistd.h>
#include <syscall.h>
#include <sys/wait.h>
#include <err.h>

int main(void){
       pid_t cpid = syscall(SYS_clone, CLONE_FS, (void*)0);
       if(cpid == -1) err(1, "SYS_clone");
       if(cpid == 0){
               /* the child, change the cwd and exit */
               execl("/bin/sh", "sh", "-c", "cd /etc", (void*)0);
               err(1, "execl /bin/sh");
       }else{
               /* the parent, wait for the child, then print the cwd */
               waitpid(-1, 0, __WALL);
               execl("/bin/sh", "sh", "-c", "pwd", (void*)0);
               err(1, "execl /bin/sh");
       }
}

這在 shell 中不起作用,因為 bourne shell(其中 bash 是一個實現)在分叉單獨的程序和執行外部命令時不使用該標誌;但是 shell 是一個過時的工作,它有很多荒謬的限制(它也沒有詞法作用域、對系統介面的直接訪問、理智和正常的語法,以及許多其他東西)。

你不應該將 45 年曆史的玩具語言的無能推斷到整個系統,它已經經歷了很多演變。


遠非自然而明顯的事情,cd“不可能”是一個外部命令(以及目前目錄的必要性!)這一事實更多地與 Unix 的逐步發展和 PDP-7 系統的限制有關它首先在其上執行。

摘自“Dennis Ritchie——Unix 分時系統的演變”

儘管它與目前文件系統相當相似,但 PDP-7 文件系統在一個方面顯著不同:沒有路徑名,系統的每個文件名參數都是一個簡單的名稱(沒有“/”)相對於目前目錄。

雖然多程序的想法確實很容易溜進來,但還是有一些沒有預料到的後遺症。在新系統出現並顯然有效後不久,其中最令人難忘的就變得明顯了。在我們歡呼雀躍的時候,發現 chdir(更改目前目錄)命令已經停止工作。對於添加 fork 可能如何破壞 chdir 呼叫,有很多程式碼閱讀和焦慮自省。終於真相大白了:在舊系統中 chdir 是一個普通的命令;它調整了附加到終端的(唯一)程序的目前目錄。在新系統下,chdir 命令正確地更改了為執行它而創建的程序的目前目錄,但該程序立即終止,對其父 shell 沒有任何影響!


來自評論:

沒有理智的 shell(執行任意外部提供的命令的程序)可以負擔使用CLONE_FS,因為它的所有子程序都會爭奪對目前目錄的控制權

這是一個謬論。這就像“我們不能允許水管工,因為不會有更多的公車車司機”。shell 顯然只能在某些情況下這樣做。孩子們已經在標准文件描述符(使標準輸入在孩子中非阻塞也使其在父中非阻塞),在目前命名空間(在子中安裝目錄也將其安裝在父中)“戰鬥” ,以及可以通過CLONE_*或其他系統介面共享或拆分的所有各種部分。“工作目錄”只是執行環境的一部分,與其說是必不可少的,不如說是一種兼容性(在每個現代系統上,都可以打開另一個目錄的相對路徑——查看所有的openat(2),linkat(2)等函式)。

子程序不應該能夠更改其父程序的工作目錄不是自然法則,但它應該完全能夠移動刪除它(因為它已經是這樣了)。50 年前的預設值不是一些黃金標準——例如,我寧願有一個 shell,預設值是執行awkjq過濾命令,它可以更改 shell 的工作目錄,而不是某種方式它可以清除我的文件(現在就是這種情況)。

引用自:https://unix.stackexchange.com/questions/615096