Minimalna, działająca reprodukcja C POSIX
Zalecam zrozumienie podstawowego interfejsu API, aby lepiej zobaczyć, co się dzieje.
spać. c
#define _XOPEN_SOURCE 700
#include <unistd.h>
int main(void) {
sleep(10000);
}
zajęty. 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);
}
Skompiluj i uruchom:
gcc -std=c99 -o sleep.out ./sleep.c
gcc -std=c99 -o busy.out ./busy.c
./sleep.out &
./busy.out
busy.out
przekazuje potwierdzenia i dane perror
wyjściowe:
Text file busy
więc wnioskujemy, że wiadomość jest zakodowana na stałe w samym glibc.
Alternatywnie:
echo asdf > sleep.out
sprawia, że wyjście Bash:
-bash: sleep.out: Text file busy
W przypadku bardziej złożonej aplikacji można to również zaobserwować za pomocą strace
:
strace ./busy.out
który zawiera:
openat(AT_FDCWD, "sleep.out", O_WRONLY) = -1 ETXTBSY (Text file busy)
Testowane na Ubuntu 18.04, jądro Linux 4.15.0.
Błąd nie występuje, jeśli unlink
pierwszy
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);
}
Następnie skompiluj i uruchom analogicznie do powyższego, a te potwierdzenia przejdą.
To wyjaśnia, dlaczego działa w niektórych programach, ale nie w innych. Np. Jeśli:
gcc -std=c99 -o sleep.out ./sleep.c
./sleep.out &
gcc -std=c99 -o sleep.out ./sleep.c
to nie generuje błędu, nawet jeśli drugie gcc
wywołanie to zapis sleep.out
.
Szybkie strace
pokazuje, że GCC pierwsze odłącza przed zapisem:
strace -f gcc -std=c99 -o sleep.out ./sleep.c |& grep sleep.out
zawiera:
[pid 3992] unlink("sleep.out") = 0
[pid 3992] openat(AT_FDCWD, "sleep.out", O_RDWR|O_CREAT|O_TRUNC, 0666) = 3
Powodem, dla którego nie kończy się niepowodzeniem, jest to, że po unlink
ponownym zapisaniu pliku tworzy on nowy i-węzeł i utrzymuje tymczasowo wiszący i-węzeł dla działającego pliku wykonywalnego.
Ale jeśli po prostu write
nie unlink
, to stara się pisać do tego samego węzła zabezpieczono jako uruchomiony wykonywalny.
POSIX 7 open()
http://pubs.opengroup.org/onlinepubs/9699919799/functions/open.html
[ETXTBSY]
Plik jest plikiem czystej procedury (współużytkowany tekst), który jest wykonywany, a oflag to O_WRONLY lub O_RDWR.
człowiek 2 otwarty
ETXTBSY
pathname odnosi się do wykonywalnego obrazu, który jest aktualnie wykonywany i zażądano dostępu do zapisu.
źródło glibc
Szybkie grep na 2.30 daje:
sysdeps/gnu/errlist.c:299: [ERR_REMAP (ETXTBSY)] = N_("Text file busy"),
sysdeps/mach/hurd/bits/errno.h:62: ETXTBSY = 0x4000001a, /* Text file busy */
i ręczne trafienie w 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
Text file busy
Błąd w szczególnych wynosi około próbuje modyfikować plik wykonywalny, gdy jest on wykonywany. „Tekst” odnosi się tutaj do faktu, że modyfikowany plik jest segmentem tekstowym dla uruchomionego programu. Jest to bardzo szczególny przypadek, a nie ogólny, który sugeruje twoja odpowiedź. Mimo to twoja odpowiedź nie jest całkowicie błędna.