What generates the “text file busy” message in Unix?

前端 未结 12 1954
渐次进展
渐次进展 2020-12-04 06:38

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

12条回答
  •  北海茫月
    2020-12-04 07:19

    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
    

提交回复
热议问题