Dlaczego niektóre wbudowane `read` powłoki nie potrafią odczytać całej linii z pliku w` / proc`?

19

W niektórych Bourne'a jak muszle, readwbudowane nie może przeczytać cały wiersz z pliku w /proc(polecenia poniżej powinny być wykonywane w zsh, wymienić $=shellz $shellinnymi muszli):

$ for shell in bash dash ksh mksh yash zsh schily-sh heirloom-sh "busybox sh"; do
  printf '[%s]\n' "$shell"
  $=shell -c 'IFS= read x </proc/sys/fs/file-max; echo "$x"'       
done
[bash]
602160
[dash]
6
[ksh]
602160
[mksh]
6
[yash]
6
[zsh]
6
[schily-sh]
602160
[heirloom-sh]
602160
[busybox sh]
6

readstandard wymaga, aby standardem był plik tekstowy , czy to wymaganie powoduje różnorodne zachowania?


Przeczytaj definicję pliku tekstowego POSIX, dokonuję weryfikacji:

$ od -t a </proc/sys/fs/file-max 
0000000   6   0   2   1   6   0  nl
0000007

$ find /proc/sys/fs -type f -name 'file-max'
/proc/sys/fs/file-max

Nie ma żadnej NULpostaci w treści /proc/sys/fs/file-max, a także findzgłosił go jako zwykły plik (czy to błąd find?).

Myślę, że skorupa zrobiła coś pod maską, na przykład file:

$ file /proc/sys/fs/file-max
/proc/sys/fs/file-max: empty
Cuonglm
źródło

Odpowiedzi:

31

Problem polega na tym, że te /procpliki w systemie Linux pojawiają się jako pliki tekstowe stat()/fstat(), ale nie zachowują się jako takie.

Ponieważ są to dane dynamiczne, możesz wykonać read()na nich tylko jedno wywołanie systemowe (przynajmniej dla niektórych). Wykonanie więcej niż jednego może dać ci dwa kawałki dwóch różnych treści, więc zamiast tego wydaje się, że sekunda read()po prostu nic nie zwraca (co oznacza koniec pliku) (chyba że lseek()wrócisz do początku (i tylko do początku)).

readNarzędzie musi odczytać zawartość plików jeden bajt na raz, aby mieć pewność, aby nie czytać obok znaku nowej linii. Właśnie to dash:

 $ strace -fe read dash -c 'read a < /proc/sys/fs/file-max'
 read(0, "1", 1)                         = 1
 read(0, "", 1)                          = 0

Niektóre powłoki bashmają optymalizację, aby uniknąć konieczności wykonywania tak wielu read()wywołań systemowych. Najpierw sprawdzają, czy plik jest widoczny, a jeśli tak, czytają fragmenty, ponieważ wiedzą, że mogą cofnąć kursor zaraz po nowej linii, jeśli ją przeczytali:

$ strace -e lseek,read bash -c 'read a' < /proc/sys/fs/file-max
lseek(0, 0, SEEK_CUR)                   = 0
read(0, "1628689\n", 128)               = 8

Dzięki bashnadal będziesz mieć problemy z plikami proc, które mają więcej niż 128 bajtów i można je odczytać tylko w jednym odczytanym wywołaniu systemowym.

bashwydaje się również wyłączać tę optymalizację, gdy -dużywana jest opcja.

ksh93posuwa optymalizację tak daleko, że staje się fałszywa. ksh93's readszuka wstecz, ale pamięta dodatkowe dane, które odczytał dla następnego read, więc następne read(lub dowolne inne jego wbudowane czytające dane, takie jak catlub head) nawet nie próbuje readdanych (nawet jeśli dane te zostały zmodyfikowane przez inne polecenia pomiędzy):

$ seq 10 > a; ksh -c 'read a; echo test > a; read b; echo "$a $b"' < a
1 2
$ seq 10 > a; sh -c 'read a; echo test > a; read b; echo "$a $b"' < a
1 st
Stéphane Chazelas
źródło
Ach tak, straceoparte na wyjaśnieniach jest znacznie łatwiejsze do zrozumienia!
Stephen Kitt
Dzięki, dynamiczne dane mają sens. Jak więc powłoka wykrywa dane dynamiczne? Jeśli tak cat /proc/sys/fs/file-max | ..., problem zniknie.
cuonglm
3
Powłoka go nie wykrywa. Fakt, że są to dane dynamiczne, oznacza, że procfsnie można obsłużyć wielu kolejnych read(2)wywołań tego samego pliku; zachowanie nie zależy od powłoki. Używanie cati catprzesyłanie potokowe działa, ponieważ czyta plik w wystarczająco dużych porcjach; readwbudowana powłoka odczytuje następnie z potoku po jednym znaku na raz.
Stephen Kitt
1
Jest trochę brudne obejście mksh. read -N 10 a < /proc/sys/fs/file-max
Ipor Sircer,
1
@IporSircer. W rzeczy samej. Podobne obejście wydaje się działać z zsh: read -u0 -k10(lub użyj sysread; $mapfile[/proc/sys/fs/file-max]nie działa, ponieważ tych plików nie można edytować mmap). W każdym razie za pomocą dowolnej powłoki zawsze można a=$(cat /proc/sys/fs/file-max). Z niektórymi tym mksh, zshi ksh93, a=$(</proc/sys/fs/file-max)również działa i nie widelec proces wspólnego czytania.
Stéphane Chazelas,
9

Jeśli chcesz wiedzieć, dlaczego? tak jest, możesz zobaczyć odpowiedź w źródłach jądra tutaj :

    if (!data || !table->maxlen || !*lenp || (*ppos && !write)) {
            *lenp = 0;
            return 0;
    }

Zasadniczo wyszukiwanie ( *pposnie 0) nie jest realizowane dla odczytów ( !write) wartości sysctl, które są liczbami. Ilekroć wykonywany jest odczyt /proc/sys/fs/file-max, dana procedura __do_proc_doulongvec_minmax()jest wywoływana z pozycji dla tabelifile-max konfiguracji w tym samym pliku.

Inne wpisy, takie jak /proc/sys/kernel/poweroff_cmdzaimplementowane, za pomocą proc_dostring()których zezwala na szukanie, dzięki czemu możesz to zrobić dd bs=1i czytać z powłoki bez żadnych problemów.

Zauważ, że ponieważ jądro 2.6 większość /procodczytów zostało zaimplementowanych za pomocą nowego API o nazwie plik_sekw. I obsługuje to wyszukiwanie, więc np. Czytanie /proc/statnie powinno powodować problemów. /proc/sys/Wdrożenie, jak widzimy, nie używa tego API.

meuh
źródło
3

Przy pierwszej próbie wygląda to jak błąd w pociskach, które zwracają mniej niż rzeczywista Powłoka Bourne'a lub jej pochodne (sh, bosh, ksh, pamiątka).

Oryginalna powłoka Bourne Shell próbuje odczytać blok (64 bajty), nowsze wersje Bourne Shell czytają 128 bajtów, ale zaczynają czytać ponownie, jeśli nie ma nowego znaku linii.

Tło: / procfs i podobne implementacje (np. Zamontowany /etc/mtabplik wirtualny) mają zawartość dynamiczną, a stat()wywołanie nie powoduje najpierw odtworzenia treści dynamicznej. Z tego powodu rozmiar takiego pliku (od odczytu do EOF) może różnić się od stat()zwracanego.

Biorąc pod uwagę, że standard POSIX wymaga narzędzi oczekujących krótkich odczytów w dowolnym momencie, oprogramowanie, które uważa, że ​​a, read()który zwraca mniej niż zamówiona ilość bajtów, jest uszkodzone. Prawidłowo zaimplementowane narzędzie wywołuje read()drugi raz w przypadku, gdy zwróci mniej niż oczekiwano - aż do zwrócenia 0. W przypadku readwbudowanego polecenia, to oczywiście być wystarczające, aby czytać aż EOF lubNLwidać.

Jeśli uruchomisz trusslub klon kratownicy, powinieneś być w stanie zweryfikować to nieprawidłowe zachowanie dla powłok, które powracają tylko 6w eksperymencie.

W tym szczególnym przypadku wydaje się, że jest to błąd jądra systemu Linux, patrz:

$ sdd -debug bs=1 if= /proc/sys/fs/file-max 
Simple copy ...
readbuf  (3, 12AC000, 1) = 1
writebuf (1, 12AC000, 1)
8readbuf  (3, 12AC000, 1) = 0

sdd: Read  1 records + 0 bytes (total of 1 bytes = 0.00k).
sdd: Wrote 1 records + 0 bytes (total of 1 bytes = 0.00k).

Jądro Linux zwraca 0 z drugim readi jest to oczywiście niepoprawne.

Wniosek: Powłoki, które najpierw próbują odczytać wystarczająco duży fragment danych, nie wyzwalają tego błędu jądra systemu Linux.

schily
źródło
OK, zakończono odpowiedź z nową weryfikacją błędu jądra systemu Linux.
schily,
To nie jest błąd, to jest funkcja!
Guntram Blohm wspiera Monikę
To naprawdę dziwne twierdzenie.
schily,
Byłoby to cechą, gdyby zostało udokumentowane. Czytając kernel.org/doc/Documentation/filesystems/proc.txt , nie widzę dokumentacji tego zachowania. To powiedziawszy, to wyraźnie działa zgodnie z zamierzeniami implementatora, więc jeśli ma to być uznany za błąd, oznacza to błąd w projekcie, a nie w implementacji.
Charles Duffy
0

Pliki w / proc czasami używają znaku NULL do oddzielenia pól w pliku. Wydaje się, że read nie jest w stanie sobie z tym poradzić.

Tony George
źródło