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 (u
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
int main(void) {
sleep(10000);
}
busy.c
#define _XOPEN_SOURCE 700
#include
#include
#include
#include
#include
#include
#include
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
#include
#include
#include
#include
#include
#include
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