[unix] What generates the "text file busy" message in Unix?

What operation generates the error "text file busy"? I am unable to tell exactly.

I think it is related to the fact that I'm creating a temporary python script (using tempfile) and using execl from it, but I think that execl changes the file being run.

This question is related to unix

The answer is


It's a while since I've seen that message, but it used to be prevalent in System V R3 or thereabouts a good couple of decades ago. Back then, it meant that you could not change a program executable while it was running.

For example, I was building a make workalike called rmk, and after a while it was self-maintaining. I would run the development version and have it build a new version. To get it to work, it was necessary to use the workaround:

gcc -g -Wall -o rmk1 main.o -L. -lrmk -L/Users/jleffler/lib/64 -ljl
if [ -f rmk ] ; then mv rmk rmk2 ; else true; fi ; mv rmk1 rmk

So, to avoid problems with the 'text file busy', the build created a new file rmk1, then moved the old rmk to rmk2 (rename wasn't a problem; unlink was), and then moved the newly built rmk1 to rmk.

I haven't seen the error on a modern system in quite a while...but I don't all that often have programs rebuilding themselves.


If you are running the .sh from a ssh connection with a tool like MobaXTerm, and if said tool has an autosave utility to edit remote file from local machine, that will lock the file.

Closing and reopening the SSH session solves it.


You may find this to be more common on CIFS/SMB network shares. Windows doesn't allow for a file to be written when something else has that file open, and even if the service is not Windows (it might be some other NAS product), it will likely reproduce the same behaviour. Potentially, it might also be a manifestation of some underlying NAS issue vaguely related to locking/replication.


One of my experience:

I always change the default keyboard shortcut of Chrome through reverse engineering. After modification, I forgot to close Chrome and ran the following:

sudo cp chrome /opt/google/chrome/chrome
cp: cannot create regular file '/opt/google/chrome/chrome': Text file busy

Using strace, you can find the more details:

sudo strace cp ./chrome /opt/google/chrome/chrome 2>&1 |grep 'Text file busy'
open("/opt/google/chrome/chrome", O_WRONLY|O_TRUNC) = -1 ETXTBSY (Text file busy)

I came across this in PHP when using fopen() on a file and then trying to unlink() it before using fclose() on it.

No good:

$handle = fopen('file.txt');
// do something
unlink('file.txt');

Good:

$handle = fopen('file.txt');
// do something
fclose($handle);
unlink('file.txt');

If trying to build phpredis on a Linux box you might need to give it time to complete modifying the file permissions, with a sleep command, before running the file:

chmod a+x /usr/bin/php/scripts/phpize \
  && sleep 1 \
  && /usr/bin/php/scripts/phpize

Minimal runnable C POSIX reproduction example

I recommend understanding the underlying API to better see what is going on.

sleep.c

#define _XOPEN_SOURCE 700
#include <unistd.h>

int main(void) {
    sleep(10000);
}

busy.c

#define _XOPEN_SOURCE 700
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(void) {
    int ret = open("sleep.out", O_WRONLY|O_TRUNC);
    assert(errno == ETXTBSY);
    perror("");
    assert(ret == -1);
}

Compile and run:

gcc -std=c99 -o sleep.out ./sleep.c
gcc -std=c99 -o busy.out ./busy.c
./sleep.out &
./busy.out 

busy.out passes the asserts, and perror outputs:

Text file busy

so we deduce that the message is hardcoded in glibc itself.

Alternatively:

echo asdf > sleep.out

makes Bash output:

-bash: sleep.out: Text file busy

For a more complex application, you can also observe it with strace:

strace ./busy.out

which contains:

openat(AT_FDCWD, "sleep.out", O_WRONLY) = -1 ETXTBSY (Text file busy)

Tested on Ubuntu 18.04, Linux kernel 4.15.0.

The error does not happen if you unlink first

notbusy.c

#define _XOPEN_SOURCE 700
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

int main(void) {
    assert(unlink("sleep.out") == 0);
    assert(open("sleep.out", O_WRONLY|O_CREAT) != -1);
}

Then compile and run analogously to the above, and those asserts pass.

This explains why it works for certain programs but not others. E.g. if you do:

gcc -std=c99 -o sleep.out ./sleep.c
./sleep.out &
gcc -std=c99 -o sleep.out ./sleep.c

that does not generate an error, even though the second gcc call is writing to sleep.out.

A quick strace shows that GCC first unlinks before writing:

 strace -f gcc -std=c99 -o sleep.out ./sleep.c |& grep sleep.out

contains:

[pid  3992] unlink("sleep.out")         = 0
[pid  3992] openat(AT_FDCWD, "sleep.out", O_RDWR|O_CREAT|O_TRUNC, 0666) = 3

The reason it does not fail is that when you unlink and re-write the file, it creates a new inode, and keeps a temporary dangling inode for the running executable file.

But if you just write without unlink, then it tries to write to the same protected inode as the running executable.

POSIX 7 open()

http://pubs.opengroup.org/onlinepubs/9699919799/functions/open.html

[ETXTBSY]

The file is a pure procedure (shared text) file that is being executed and oflag is O_WRONLY or O_RDWR.

man 2 open

ETXTBSY

pathname refers to an executable image which is currently being executed and write access was requested.

glibc source

A quick grep on 2.30 gives:

sysdeps/gnu/errlist.c:299:    [ERR_REMAP (ETXTBSY)] = N_("Text file busy"),
sysdeps/mach/hurd/bits/errno.h:62:  ETXTBSY                        = 0x4000001a,        /* Text file busy */

and a manual hit in manual/errno.texi:

@deftypevr Macro int ETXTBSY
@standards{BSD, errno.h}
@errno{ETXTBSY, 26, Text file busy}
An attempt to execute a file that is currently open for writing, or
write to a file that is currently being executed.  Often using a
debugger to run a program is considered having it open for writing and
will cause this error.  (The name stands for ``text file busy''.)  This
is not an error on @gnuhurdsystems{}; the text is copied as necessary.
@end deftypevr

root@h1:bin[0]# mount h2:/ /x             
root@h1:bin[0]# cp /usr/bin/cat /x/usr/local/bin/
root@h1:bin[0]# umount /x
...
root@h2:~[0]# /usr/local/bin/cat 
-bash: /usr/local/bin/cat: Text file busy
root@h2:~[126]#

ubuntu 20.04, 5.4.0-40-generic
nfsd problem, after reboot ok

In my case, I was trying to execute a shell file (with an extension .sh) in a csh environment, and I was getting that error message.

just running with bash it worked for me. For example

bash file.sh


This occurs when you try and write to a file that is currently being executed by the kernel, or execute a file that is currently open for writing.

Source: http://wiki.wlug.org.nz/ETXTBSY


Don't know the cause but I can contribute a quick and easy work around.

I just experienced this this oddity on CentOS 6 after cat > shScript.sh (paste, ^Z) then editing the file in KWrite. Oddly there was no discernible instance (ps -ef) of the script executing.

My quick work around was simply to cp shScript.sh shScript2.sh then I was able to execute shScript2.sh. Then I deleted both. Done!