Wann ist dd zum Kopieren von Daten geeignet? (oder wenn read () und write () partiell sind)

60

Kurzfassung: Unter welchen Umständen ist dddie Verwendung zum Kopieren von Daten sicher, dh es besteht keine Gefahr der Beschädigung durch teilweises Lesen oder Schreiben?

Lange Version - Präambel: Wird dd häufig zum Kopieren von Daten verwendet, insbesondere von oder auf ein Gerät ( Beispiel ). Es wird manchmal mystischen Eigenschaften zugeschrieben, auf Geräte auf einer niedrigeren Ebene als andere Tools zugreifen zu können (obwohl es tatsächlich die Gerätedatei ist, die die Magie ausübt) - und doch dd if=/dev/sdaist es dasselbe wie cat /dev/sda. ddwird manchmal für schneller gehalten, catkann es aber in der Praxis übertreffen . Trotzdem ddhat es einzigartige Eigenschaften, die es manchmal wirklich nützlich machen .

Problem: dd if=foo of=bar ist in der Tat nicht das gleiche wie cat <foo >bar. Bei den meisten Geräten ddwird ein einzelner Anruf an getätigt read(). (Ich finde POSIX unscharf, wenn es sich um das Einlesen eines Eingabeblocks handelt dd.) Wenn read()ein Teilergebnis zurückgegeben wird (das laut POSIX und anderen Referenzdokumenten zulässig ist, sofern in der Implementierungsdokumentation nichts anderes angegeben ist), wird ein Teilblock kopiert. Genau das gleiche Problem besteht für write().

Beobachtungen : In der Praxis habe ich festgestellt, dass ddmit Blockgeräten und regulären Dateien umgegangen werden kann, aber das kann nur sein, dass ich nicht viel damit gemacht habe. Wenn es um Rohre geht, ist es nicht schwer, ddFehler zu machen. Versuchen Sie zum Beispiel diesen Code :

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

und überprüfen Sie die Größe der outDatei (es ist wahrscheinlich deutlich unter 10 MB).

Frage : Unter welchen Umständen ist dddie Verwendung zum Kopieren von Daten sicher? Mit anderen Worten, welche Bedingungen bezüglich der Blockgrößen, der Implementierung, der Dateitypen usw. können sicherstellen, dass ddalle Daten kopiert werden?

( GNU dd hat ein fullblockFlag, das es aufruft read()oder sich write()in einer Schleife befindet, um einen vollständigen Block zu übertragen. Das dd iflag=fullblockist also immer sicher. Meine Frage bezieht sich auf den Fall, dass diese Flags (die in anderen Implementierungen nicht vorhanden sind) nicht verwendet werden .)

¹ Ich habe OpenBSD, GNU Coreutils und BusyBox überprüft.

Gilles 'SO - hör auf böse zu sein'
quelle
Ich habe noch nie ein Unixy-System gesehen, das wirklich ein paar MiB in einem einzigen
Lesevorgang
3
Bei Verwendung von countist das iflag=fullblockobligatorisch (oder alternativ iflag=count_bytes). Es gibt keine oflag=fullblock.
Frostschutz

Antworten:

10

Aus der Spezifikation :

  • Wenn der bs=exprOperand angegeben ist und keine Konvertierungen ausgenommen sync, noerroroder notruncangefordert werden , die von jedem Eingangsblock zurückgegebenen Daten werden als separater Ausgangsblock geschrieben werden; Wenn der read()Rückgabewert kleiner als ein vollständiger Block ist und die syncKonvertierung nicht angegeben ist, muss der resultierende Ausgabeblock dieselbe Größe wie der Eingabeblock haben.

Das ist wahrscheinlich der Grund für Ihre Verwirrung. Ja, denn ddist ausgelegt für die Sperrung, die standardmäßig Teil read()s wird abgebildet 1: 1 zu Teil- write()s, oder auch syncauf Schwanz Polsterung NUL oder Raum Zeichen auf d out bs=Größe , wenn conv=syncangegeben wird.

Dies bedeutet, dass die Verwendung zum Kopieren von Daten sicherdd ist (ohne Verfälschungsrisiko aufgrund eines teilweisen Lese- oder Schreibvorgangs), jedoch in jedem Fall, in dem sie willkürlich durch ein count=Argument begrenzt werden , da ansonsten dddie write()Ausgabe gerne in gleich großen Blöcken erfolgt zu denen, in denen seine Eingabe war, read()bis es read()vollständig durch es ist. Und selbst diese Einschränkung nur wahr , wenn bs=angegeben wird , oder obs=wird nicht angegeben, wie der nächste Satz in dem spec heißt es :

  • Wenn der bs=exprOperand nicht angegeben, oder eine Umwandlung außer sync, noerroroder notruncbeantragt wird, muss der Eingang verarbeitet und wird in volle Größe Ausgabeblocks gesammelt , bis das Ende der Eingabe erreicht ist.

Ohne ibs=und / oder obs=Argument kann dies keine Rolle - da ibsund obsbeide die gleiche Größe in der Standardeinstellung. Sie können sich jedoch explizit über die Eingabepufferung informieren, indem Sie entweder unterschiedliche Größen angeben oder keine angeben bs= (da dies Vorrang hat) .

Zum Beispiel, wenn Sie:

IN| dd ibs=1| OUT

... dann ein POSIX ddwird write()in Blöcken von 512 Bytes , die durch das Sammeln jeden einzeln read()Byte in einen einzigen Ausgangsblock.

Ansonsten, wenn Sie tun ...

IN| dd obs=1kx1k| OUT

... a POSIX ddwird read() bei maximal 512 Byte zu einer Zeit, aber write()jeder Megabyte-sized Ausgabeblock (kernel ermöglicht und mit Ausnahme vielleicht die letzten - weil das EOF ist) vollständig durch Eingabe in dem Sammeln voller Größe Ausgangsblocks .

Aber auch aus der Spezifikation:

  • count=n
    • Kopieren Sie nur n Eingabeblöcke.

count=Ordnet i?bs=Blöcke zu, und um eine willkürliche Beschränkung für count=portabel zu handhaben, benötigen Sie zwei dds. Der praktischste Weg, dies mit zwei dds zu tun, besteht darin, die Ausgabe von einem in die Eingabe eines anderen zu leiten, was uns sicherlich in den Bereich des Lesens / Schreibens einer speziellen Datei versetzt, unabhängig vom ursprünglichen Eingabetyp.

Eine IPC-Pipe bedeutet, dass Sie bei der Angabe von [io]bs=Argumenten, um dies sicher zu tun, diese Werte innerhalb des vom System festgelegten PIPE_BUFGrenzwerts halten müssen. POSIX besagt, dass der Systemkern nur Atome read()und write()Atome innerhalb der PIPE_BUFin definierten Grenzen garantieren darf limits.h. POSIX garantiert, PIPE_BUFdass mindestens ...

  • {_POSIX_PIPE_BUF}
    • Maximale Anzahl von Bytes, die beim Schreiben in eine Pipe garantiert atomar sind.
    • Wert: 512

... (was auch die Standard- ddE / A-Blockgröße ist) , aber der tatsächliche Wert beträgt normalerweise mindestens 4 KB . Auf einem aktuellen Linux-System sind es standardmäßig 64 KB.

Wenn Sie also Ihre ddProzesse einrichten, sollten Sie einen Blockfaktor verwenden , der auf drei Werten basiert:

  1. bs = (obs = PIPE_BUFoder kleiner)
  2. n = gewünschte Gesamtzahl der gelesenen Bytes
  3. count = n / bs

Mögen:

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

Sie müssen i / ow / synchronisieren dd, um nicht suchbare Eingaben zu verarbeiten. Mit anderen Worten, machen Sie Pipe-Puffer explizit und sie hören auf, ein Problem zu sein. Dafür ddist es da. Die unbekannte Größe ist hier yesdie Puffergröße - aber wenn Sie diese mit einer anderen auf eine bekannte Größe beschränken, ddkann ein wenig informierte Multiplikation dd sicheres Kopieren von Daten ermöglichen (ohne Verfälschungsrisiko aufgrund eines teilweisen Lese- oder Schreibvorgangs). Selbst wenn die Eingabe mit count=einem beliebigen Eingabetyp auf einem beliebigen POSIX-System willkürlich eingeschränkt wird und kein einziges Byte fehlt.

Hier ist ein Ausschnitt aus der POSIX-Spezifikation :

  • ibs=expr
    • Geben Sie die Größe des Eingabeblocks in Byte an (Standard ist 512) .expr
  • obs=expr
    • Geben Sie die Ausgabeblockgröße in Byte an (Standard ist 512) .expr
  • bs=expr
    • Stellen Sie sowohl die Eingabe- als auch die Ausgabeblockgröße auf exprBytes ein und ersetzen Sie ibs=und obs=. Wenn keine andere Konvertierung als sync, noerrorund notruncangegeben ist, wird jeder Eingabeblock als einzelner Block in die Ausgabe kopiert, ohne kurze Blöcke zu aggregieren.

Einige Erklärungen dazu finden Sie auch hier .

mikeserv
quelle
5

Mit Sockets, Pipes oder ttys können read () und write () weniger als die angeforderte Größe übertragen. Wenn Sie dd für diese verwenden, benötigen Sie das Fullblock-Flag. Bei regulären Dateien und Blockgeräten gibt es jedoch nur zwei Fälle, in denen sie einen kurzen Lese- / Schreibvorgang ausführen können: Wenn Sie EOF erreichen oder wenn ein Fehler auftritt. Aus diesem Grund konnten ältere Implementierungen von dd ohne das Fullblock-Flag sicher für die Datenträgerduplizierung verwendet werden.

Psusi
quelle
Gilt das für alle modernen Unices? (Ich weiß , dass es irgendwann nicht wahr von Linux war, bis vielleicht auf 2.0.x oder 2.2.x ich rembember mke2fsandernfalls still , weil es genannt write()mit einigem nicht-Power-of-2 Größe (3kB IIRC) und die Kernel gerundet bis auf eine Potenz von 2.)
Gilles 'SO - hör auf böse zu sein'
@ Gilles das klingt nach einem ganz anderen Thema. Bei Blockgeräten muss immer ein Vielfaches der richtigen Blockgröße verwendet werden. Ich bin mir ziemlich sicher, dass dies für alle Betriebssysteme und auch für Windows zutrifft.
Psusi
Abgesehen von Bändern ist die Blockgröße eines Geräts nur für den Kernel wichtig oder nicht. cat </dev/sda >/dev/sdbfunktioniert gut, um eine Festplatte zu klonen.
Gilles 'SO- hör auf böse zu sein'
@Gilles, weil cat die entsprechende Blockgröße verwendet, wie OrbWeaver in seiner Antwort angemerkt hat.
Psusi
Nein, es gibt keine "passende Blockgröße". catwählt eine Puffergröße für die Leistung aus; Es werden keine gerätebezogenen Informationen vom Kernel abgerufen. Abgesehen von Bändern können Sie read()und write()zu einem Blockgerät mit beliebiger Größe. Zumindest unter Linux st_blksizehängt dies nur vom Dateisystem ab, in dem sich der Blockgeräte-Inode befindet, nicht vom zugrunde liegenden Gerät.
Gilles 'SO- hör auf böse zu sein'