0

Why we can't create a process which change the current working dir. Just like cd command does?

2
  • 4
    You can't change the current working directory of another process. cd is a builtin command because an external executable can't change the cwd of the shell.
    – Oskar Skog
    Oct 18 '20 at 12:35
  • 2
    Related: Why is cd not a program?
    – fra-san
    Oct 18 '20 at 14:31
4

Think about what you're asking. You have a shell, and you want to write an external program that will change the shell's cwd. Does that even make sense? To me it sounds like you're not quite groking the way a shell is just a program that accepts a string and spawns some subprocesses (which naturally inherit the cwd of their parent, the shell).

I suspect you could do it hackishly, perhaps by using ptrace to inject commands into the shell process. You could write your own shell that kept a distinct setting for "child wd" (the main change is that after it forks a child, it would need to remember to chdir()). You could write a kernel module that would let you forcibly change an arbitrary process's cwd, though I expect that would break things more than it would be useful.

If this kind of thing made any sense, you could (with appropriate permissions), do "ln -fs /somewhere /proc/$pid/cwd". When I try that as root, I get "ln: cwd/: cannot overwrite directory" - from strace, it looks like it's checking and noticing that the target already exists. symlink() appears to fail in that case, so the symlink command, would presumably have to remove the existing link first, and I don't think the kernel would let that happen.

1

You cannot change the current working directory in a child process because ... you can actually do it just fine.

The clone(2) Linux system call which is used to create a process (and implement legacy interfaces like fork(2)) has a CLONE_FS flag which can be used to let the child share the root directory, current working directory and umask with its parent:

#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");
        }
}

That does not work in the shell because the bourne shell (of which bash is an implementation) doesn't use that flag when forking separate processes and running external commands; but the shell is a antiquated piece of work which has a LOT of absurd limitations (it also doesn't have lexical scoping, direct access to the system's interfaces, a sane and regular syntax, and many many other things).

You should not extrapolate the ineptness of a 45 years old toy-language to the entire system, which has seen a lot of evolution.


Far from being something something natural and obvious, the fact that cd "could not" be an external command (and the very necessity of a current directory!) has more to do with the stepwise evolution of Unix and the constraints of the PDP-7 system on which it first ran.

From "Dennis Ritchie -- The Evolution of the Unix Time-Sharing System":

In spite of its considerable similarity to the current file system, the PDP-7 file system was in one way remarkably different: there were no path names, and each file-name argument to the system was a simple name (without ‘/’) taken relative to the current directory.

...

Although the multiple-process idea slipped in very easily indeed, there were some aftereffects that weren’t anticipated. The most memorable of these became evident soon after the new system came up and apparently worked. In the midst of our jubilation, it was discovered that the chdir (change current directory) command had stopped working. There was much reading of code and anxious introspection about how the addition of fork could have broken the chdir call. Finally the truth dawned: in the old system chdir was an ordinary command; it adjusted the current directory of the (unique) process attached to the terminal. Under the new system, the chdir command correctly changed the current directory of the process created to execute it, but this process promptly terminated and had no effect whatsoever on its parent shell!


From comments:

No sane shell (a program executing arbitrary externally-supplied commands) can afford to use CLONE_FS, as then all its child processes would fight over the control of the current directory

That's a fallacy. It's like "we cannot allow plumbers because there would be no more bus drivers". The shell could obviously do that only in some cases. The children already "fight" over the standard file descriptors (making the stdin non-blocking in the child also makes it non-blocking in the parent), over the current namespace (mounting a directory in the child also mounts it in the parent), and all the various pieces which could be shared or split via CLONE_* or other system interfaces. The "working directory" is just a piece of the execution environment, and rather a compatibility than an essential one (on every modern system it's possible to open relative paths wrt another directory -- see all the openat(2), linkat(2), etc functions).

It's not some law of nature that a child process should not be able to change the working directory of its parent, but it should be perfectly able to move or remove it (as it already is). The default of 50 years ago is not some golden standard -- for instance, I would rather have a shell where the default would be to run an awk or jq filter command in way in which it could change the shell's working directory, rather than in a way in which it could wipe off my files (which is the case now).

4
  • 2
    While your point about CLONE_FS is really good, you should IMHO reconsider the following paragraphs blaming the "antiquated shell" for not using it. No sane shell (a program executing arbitrary externally-supplied commands) can afford to use CLONE_FS, as then all its child processes would fight over the control of the current directory. That'd break absolutely everything and make the whole notion of "current directory" practically useless for most purposes, as the whole system would essentially be locked into using the same current dir.
    – TooTea
    Oct 19 '20 at 15:31
  • 2
    Additionally clone(2) is Linux-specific.
    – Oskar Skog
    Oct 19 '20 at 16:37
  • 2
    Oh dear. A lot of users would be really confused, if running something that recursively works through a directory tree (and perhaps killing it in the middle of it) left the shell in some random directory inside that tree. That would also cause some interesting effects with processes sent to the background, e.g. a recursive copy or tar operation. Let alone "daemon"-like processes that are supposed to be in the background for a long time.
    – ilkkachu
    Oct 20 '20 at 7:53
  • 2
    Also note how that quote from Richie doesn't continue with a description of how they promptly made it so that all processes would have a common working directory, but instead just fixed chdir into a builtin. As for the absurd limitations of the shell language, they hardly seem related, as it's quite possible to write a shell with a better language but still using fork(), or just fix Bash to use clone(CLONE_FS). Heck, Bash is still maintained, and new versions are coming out, so you could even post that patch upstream.
    – ilkkachu
    Oct 20 '20 at 7:58
1

Processes are the main unit of doing anything on the computer. They encapsulate things like virtual memory and its contents, the state of CPU registers and the instruction pointer (pointer to where the code is running), and on the OS level, things like open files and the working directory. These are things a running process needs to be unchanged by external events for the program logic to work.

If the CPU registers or the instruction pointer changed when the program was running, very unexpected things would happen. Similarly, changing open files from under the program might e.g. cause an HTTP server to suddenly switch to serving the wrong file, or to read from a wrong configuration file without even knowing about it. And changing the working directory of a process would cause it to find the wrong files if it used relative paths. Or remove them; think what happened if a running rm -rf somedir was suddenly moved to another directory: it would start removing files from the wrong place.

Hence, it's a not a good idea to allow doing that without the cooperation of the process itself. You could of course create a program that changes its own working directory, but having another program do that for the shell would not be a very good idea. Also it's not necessary, since the shell can well do it itself with a builtin command, and if you need to customize it, you can do it with a shell function. Possibly using an external program to choose the directory to change to, while still having the shell's own cd to do the actual change. E.g. think something like cdfoo() { cd -- "$(get_foo_dir "$1")"; }.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.