Kiedy dd nadaje się do kopiowania danych? (lub, gdy są czytane () i write () częściowe)

60

Wersja skrócona: w jakich okolicznościach można ddbezpiecznie kopiować dane, co oznacza, że ​​nie ma ryzyka uszkodzenia z powodu częściowego odczytu lub zapisu?

Wersja długa - preambuła: dd jest często używana do kopiowania danych, szczególnie z urządzenia lub na urządzenie ( przykład ). Czasami przypisuje się jej mistyczne właściwości dostępu do urządzeń na niższym poziomie niż inne narzędzia (kiedy tak naprawdę to plik urządzenia robi magię) - a jednak dd if=/dev/sdato samo cat /dev/sda. ddczasami uważa się, że jest szybszy, ale catmoże go pokonać w praktyce . Niemniej jednak ddma unikalne właściwości, które sprawiają, że czasami jest naprawdę przydatny .

Problem: dd if=foo of=bar w rzeczywistości nie jest taki sam jak cat <foo >bar. W większości jednorożców¹ ddwykonuje pojedyncze połączenie z read(). (Uważam, że POSIX jest rozmyślny na temat tego, co stanowi „odczyt bloku wejściowego” dd.) Jeśli read()zwraca wynik częściowy (który, zgodnie z POSIX i innymi dokumentami referencyjnymi, jest dozwolony, chyba że dokumentacja implementacyjna mówi inaczej), blok częściowy jest kopiowany. Istnieje dokładnie ten sam problem write().

Uwagi : W praktyce odkryłem, że ddradzi sobie z urządzeniami blokowymi i zwykłymi plikami, ale może to być po prostu to, że nie ćwiczyłem zbyt wiele. Jeśli chodzi o rury, nie jest trudno ddwinić; na przykład wypróbuj ten kod :

yes | dd of=out bs=1024k count=10

i sprawdź rozmiar outpliku (prawdopodobnie będzie miał mniej niż 10 MB).

Pytanie : W jakich okolicznościach można ddbezpiecznie używać do kopiowania danych? Innymi słowy, jakie warunki dotyczące rozmiarów bloków, implementacji, typów plików itp. Mogą zapewnić ddskopiowanie wszystkich danych?

( GNU dd ma fullblockflagę, która nakazuje mu wywołać read()lub write()w pętli, aby przekazać pełny blok. dd iflag=fullblockJest więc zawsze bezpieczny. Moje pytanie dotyczy przypadku, gdy te flagi (które nie istnieją w innych implementacjach) nie są używane .)

¹ Sprawdziłem OpenBSD, GNU coreutils i BusyBox.

Gilles
źródło
Nigdy nie widziałem żadnego systemu uniksowego, który naprawdę potrafiłby odczytać kilka MiB w jednym czytaniu (2) ...
vonbrand,
3
W przypadku korzystania z countThe iflag=fullblockjest obowiązkowe (lub alternatywnie iflag=count_bytes). Nie ma oflag=fullblock.
frostschutz

Odpowiedzi:

10

Ze specyfikacji :

  • Jeżeli bs=exprargument jest określony i nie są wymagane żadne konwersje inne niż sync, noerrorlub dane notruncsą wymagane, dane zwrócone z każdego bloku wejściowego zapisuje się jako osobny blok wyjściowy; jeżeli read()zwraca mniej niż pełny blok, a synckonwersja nie jest określona, ​​wynikowy blok wyjściowy ma taki sam rozmiar jak blok wejściowy.

Prawdopodobnie to właśnie powoduje twoje zamieszanie. Tak, ponieważ ddjest przeznaczony do blokowania, domyślnie częściowe read()s zostaną zamapowane 1: 1 na częściowe write(), lub syncd na wypełnieniu ogona NUL lub znakami spacji do bs=rozmiaru, gdy conv=synczostanie określony.

Oznacza to, że ddmożna bezpiecznie używać do kopiowania danych (bez ryzyka uszkodzenia z powodu częściowego odczytu lub zapisu) w każdym przypadku, z wyjątkiem jednego, w którym jest on arbitralnie ograniczony przez count=argument, ponieważ w przeciwnym razie jego dane wyjściowe w blokach o identycznych rozmiarach ddbędą szczęśliwie write()do tych, w których jego wkład był, read()dopóki nie read()przejdzie przez to całkowicie. I nawet to zastrzeżenie jest prawdziwe tylko wtedy, gdy bs=jest określone lub nieobs= jest określone, ponieważ następne zdanie w specyfikacji stwierdza:

  • Jeżeli bs=exprargument nie jest określony lub wymagana jest konwersja inna niż sync, noerrorlub notruncwymagane jest, dane wejściowe będą przetwarzane i gromadzone w pełnowymiarowych blokach wyjściowych, aż do osiągnięcia końca danych wejściowych.

Bez argumentów ibs=i / lub obs=nie ma to znaczenia - ponieważ ibsi obsoba mają domyślnie ten sam rozmiar. Możesz jednak uzyskać wyraźne informacje na temat buforowania danych wejściowych, określając dla nich różne rozmiary i nie określając bs= (ponieważ ma to pierwszeństwo) .

Na przykład, jeśli wykonasz:

IN| dd ibs=1| OUT

... wtedy POSIX ddbędzie write()składał się z 512 bajtów, zbierając każdy pojedynczy read()bajt w pojedynczy blok wyjściowy.

W przeciwnym razie, jeśli zrobisz ...

IN| dd obs=1kx1k| OUT

... POSIX ddbędzie miał jednocześnie read() maksymalnie 512 bajtów, ale write()każdy blok wyjściowy wielkości megabajta (jądro pozwala i może z wyjątkiem ostatniego - ponieważ to jest EOF) w całości, zbierając dane wejściowe do pełnowymiarowych bloków wyjściowych .

Jednak również ze specyfikacji:

  • count=n
    • Kopiuj tylko n bloków wejściowych.

count=mapuje na i?bs=bloki, więc aby poradzić sobie z dowolnym ograniczeniem count=przenośnym, potrzebujesz dwóch dds. Najbardziej praktycznym sposobem na zrobienie tego za pomocą dwóch dds jest podłączenie wyjścia jednego z nich do wejścia drugiego, co z pewnością stawia nas w sferze czytania / pisania specjalnego pliku, niezależnie od pierwotnego typu wejścia.

Rura IPC oznacza, że ​​przy określaniu [io]bs=argumentów, że aby to zrobić bezpiecznie, musisz utrzymać takie wartości w ramach zdefiniowanego PIPE_BUFlimitu systemu. POSIX stwierdza, że jądro systemu musi zagwarantować tylko atomowe read()S i write()S w granicach PIPE_BUF, jak określono w limits.h. Gwarancje POSIX, które PIPE_BUFbędą co najmniej ...

  • {_POSIX_PIPE_BUF}
    • Maksymalna liczba bajtów, która jest gwarantowana jako niepodzielna podczas zapisywania na potoku.
    • Wartość: 512

... (który również jest domyślnym ddrozmiarem bloku we / wy) , ale rzeczywista wartość wynosi zwykle co najmniej 4k. W aktualnym systemie Linux jest to domyślnie 64k.

Więc kiedy konfigurujesz swoje ddprocesy, powinieneś to zrobić na podstawie współczynnika blokowego opartego na trzech wartościach:

  1. bs = (obs = PIPE_BUFlub mniej)
  2. n = całkowita pożądana liczba odczytanych bajtów
  3. count = n / bs

Lubić:

yes | dd obs=1k | dd bs=1k count=10k of=/dev/null
10240+0 records in
10240+0 records out
10485760 bytes (10 MB) copied, 0.1143 s, 91.7 MB/s

Musisz zsynchronizować i / ow /, ddaby obsłużyć niewidoczne dane wejściowe. Innymi słowy, wyraź bufory potoku i przestań być problemem. Po to ddjest. Nieznana ilość tutaj jest yeswielkości bufora - ale jeśli zablokujesz ją na znanej ilości z inną, ddto małe, świadome zwielokrotnienie może dd bezpiecznie wykorzystać do kopiowania danych (bez ryzyka uszkodzenia z powodu częściowego odczytu lub zapisu) nawet gdy arbitralnie ograniczasz wejście w / count=w / dowolny typ wejścia w dowolnym systemie POSIX i bez utraty jednego bajtu.

Oto fragment specyfikacji POSIX :

  • ibs=expr
    • Określ rozmiar bloku wejściowego, w bajtach, do (domyślnie 512) .expr
  • obs=expr
    • Określ rozmiar bloku wyjściowego, w bajtach, do (domyślnie 512) .expr
  • bs=expr
    • Ustaw rozmiar bloku wejściowego i wyjściowego na exprbajty, zastępując ibs=i obs=. Jeśli konwersja nie inna niż sync, noerrori notruncjest określona, każdy blok wejściowy zostanie skopiowany na wyjście jako pojedynczy blok bez agregacji krótkie bloki.

Znajdziesz tu także niektóre to lepiej wyjaśnione tutaj .

mikeserv
źródło
5

W przypadku gniazd, potoków lub ttys read () i write () mogą przesyłać mniej niż żądany rozmiar, więc jeśli używasz na nich dd, potrzebujesz flagi fullblock. Jednak w przypadku zwykłych plików i urządzeń blokujących są tylko dwa razy, gdy mogą wykonać krótki odczyt / zapis: po osiągnięciu EOF lub w przypadku błędu. To dlatego starsze implementacje dd bez flagi fullblock były bezpieczne do powielania dysków.

psusi
źródło
Czy to prawda o wszystkich współczesnych jednorożcach? (Wiem, że w pewnym momencie nie było to prawdą w przypadku Linuksa, być może do wersji 2.0.x lub 2.2.x. Pamiętam, że zawiodłem mke2fspo cichu, ponieważ wywołano go write()z jakimś innym formatem niż power-of-2 (3kB IIRC) i jądro zostało zaokrąglone do potęgi 2.)
Gilles
@Gilles brzmi to zupełnie inaczej. Zawsze musisz użyć wielokrotności odpowiedniego rozmiaru bloku z urządzeniami blokowymi. Jestem prawie pewien, że dotyczy to wszystkich jednorożców i dotyczy to także systemu Windows.
psusi
Poza taśmami rozmiar bloku urządzenia zależy wyłącznie od jądra. cat </dev/sda >/dev/sdbdziała dobrze, aby sklonować dysk.
Gilles,
@Gilles, ponieważ kot używa odpowiedniego rozmiaru bloku, jak zauważył OrbWeaver w swojej odpowiedzi.
psusi
Nie, nie ma „odpowiedniego rozmiaru bloku”. catwybiera rozmiar bufora dla wydajności; nie otrzymuje żadnych informacji związanych z urządzeniem z jądra. Oprócz taśm, możesz read()i write()do urządzenia blokowego o dowolnym rozmiarze. Przynajmniej w systemie Linux st_blksizezależy tylko od systemu plików, w którym znajduje się i-węzeł urządzenia blokowego, a nie od urządzenia bazowego.
Gilles,