Kopieren Sie eine Datei auf vernünftige, sichere und effiziente Weise


305

Ich suche nach einer guten Möglichkeit, eine Datei (Binärdatei oder Text) zu kopieren. Ich habe mehrere Beispiele geschrieben, jeder arbeitet. Aber ich möchte die Meinung erfahrener Programmierer hören.

Ich vermisse gute Beispiele und suche einen Weg, der mit C ++ funktioniert.

ANSI-C-WAY

#include <iostream>
#include <cstdio>    // fopen, fclose, fread, fwrite, BUFSIZ
#include <ctime>
using namespace std;

int main() {
    clock_t start, end;
    start = clock();

    // BUFSIZE default is 8192 bytes
    // BUFSIZE of 1 means one chareter at time
    // good values should fit to blocksize, like 1024 or 4096
    // higher values reduce number of system calls
    // size_t BUFFER_SIZE = 4096;

    char buf[BUFSIZ];
    size_t size;

    FILE* source = fopen("from.ogv", "rb");
    FILE* dest = fopen("to.ogv", "wb");

    // clean and more secure
    // feof(FILE* stream) returns non-zero if the end of file indicator for stream is set

    while (size = fread(buf, 1, BUFSIZ, source)) {
        fwrite(buf, 1, size, dest);
    }

    fclose(source);
    fclose(dest);

    end = clock();

    cout << "CLOCKS_PER_SEC " << CLOCKS_PER_SEC << "\n";
    cout << "CPU-TIME START " << start << "\n";
    cout << "CPU-TIME END " << end << "\n";
    cout << "CPU-TIME END - START " << end - start << "\n";
    cout << "TIME(SEC) " << static_cast<double>(end - start) / CLOCKS_PER_SEC << "\n";

    return 0;
}

POSIX-WAY (K & R verwendet dies in "The C Programming Language", niedriger)

#include <iostream>
#include <fcntl.h>   // open
#include <unistd.h>  // read, write, close
#include <cstdio>    // BUFSIZ
#include <ctime>
using namespace std;

int main() {
    clock_t start, end;
    start = clock();

    // BUFSIZE defaults to 8192
    // BUFSIZE of 1 means one chareter at time
    // good values should fit to blocksize, like 1024 or 4096
    // higher values reduce number of system calls
    // size_t BUFFER_SIZE = 4096;

    char buf[BUFSIZ];
    size_t size;

    int source = open("from.ogv", O_RDONLY, 0);
    int dest = open("to.ogv", O_WRONLY | O_CREAT /*| O_TRUNC/**/, 0644);

    while ((size = read(source, buf, BUFSIZ)) > 0) {
        write(dest, buf, size);
    }

    close(source);
    close(dest);

    end = clock();

    cout << "CLOCKS_PER_SEC " << CLOCKS_PER_SEC << "\n";
    cout << "CPU-TIME START " << start << "\n";
    cout << "CPU-TIME END " << end << "\n";
    cout << "CPU-TIME END - START " << end - start << "\n";
    cout << "TIME(SEC) " << static_cast<double>(end - start) / CLOCKS_PER_SEC << "\n";

    return 0;
}

KISS-C ++ - Streambuffer-WAY

#include <iostream>
#include <fstream>
#include <ctime>
using namespace std;

int main() {
    clock_t start, end;
    start = clock();

    ifstream source("from.ogv", ios::binary);
    ofstream dest("to.ogv", ios::binary);

    dest << source.rdbuf();

    source.close();
    dest.close();

    end = clock();

    cout << "CLOCKS_PER_SEC " << CLOCKS_PER_SEC << "\n";
    cout << "CPU-TIME START " << start << "\n";
    cout << "CPU-TIME END " << end << "\n";
    cout << "CPU-TIME END - START " <<  end - start << "\n";
    cout << "TIME(SEC) " << static_cast<double>(end - start) / CLOCKS_PER_SEC << "\n";

    return 0;
}

COPY-ALGORITHM-C ++ - WEG

#include <iostream>
#include <fstream>
#include <ctime>
#include <algorithm>
#include <iterator>
using namespace std;

int main() {
    clock_t start, end;
    start = clock();

    ifstream source("from.ogv", ios::binary);
    ofstream dest("to.ogv", ios::binary);

    istreambuf_iterator<char> begin_source(source);
    istreambuf_iterator<char> end_source;
    ostreambuf_iterator<char> begin_dest(dest); 
    copy(begin_source, end_source, begin_dest);

    source.close();
    dest.close();

    end = clock();

    cout << "CLOCKS_PER_SEC " << CLOCKS_PER_SEC << "\n";
    cout << "CPU-TIME START " << start << "\n";
    cout << "CPU-TIME END " << end << "\n";
    cout << "CPU-TIME END - START " <<  end - start << "\n";
    cout << "TIME(SEC) " << static_cast<double>(end - start) / CLOCKS_PER_SEC << "\n";

    return 0;
}

OWN-BUFFER-C ++ - WEG

#include <iostream>
#include <fstream>
#include <ctime>
using namespace std;

int main() {
    clock_t start, end;
    start = clock();

    ifstream source("from.ogv", ios::binary);
    ofstream dest("to.ogv", ios::binary);

    // file size
    source.seekg(0, ios::end);
    ifstream::pos_type size = source.tellg();
    source.seekg(0);
    // allocate memory for buffer
    char* buffer = new char[size];

    // copy file    
    source.read(buffer, size);
    dest.write(buffer, size);

    // clean up
    delete[] buffer;
    source.close();
    dest.close();

    end = clock();

    cout << "CLOCKS_PER_SEC " << CLOCKS_PER_SEC << "\n";
    cout << "CPU-TIME START " << start << "\n";
    cout << "CPU-TIME END " << end << "\n";
    cout << "CPU-TIME END - START " <<  end - start << "\n";
    cout << "TIME(SEC) " << static_cast<double>(end - start) / CLOCKS_PER_SEC << "\n";

    return 0;
}

LINUX-WAY // erfordert Kernel> = 2.6.33

#include <iostream>
#include <sys/sendfile.h>  // sendfile
#include <fcntl.h>         // open
#include <unistd.h>        // close
#include <sys/stat.h>      // fstat
#include <sys/types.h>     // fstat
#include <ctime>
using namespace std;

int main() {
    clock_t start, end;
    start = clock();

    int source = open("from.ogv", O_RDONLY, 0);
    int dest = open("to.ogv", O_WRONLY | O_CREAT /*| O_TRUNC/**/, 0644);

    // struct required, rationale: function stat() exists also
    struct stat stat_source;
    fstat(source, &stat_source);

    sendfile(dest, source, 0, stat_source.st_size);

    close(source);
    close(dest);

    end = clock();

    cout << "CLOCKS_PER_SEC " << CLOCKS_PER_SEC << "\n";
    cout << "CPU-TIME START " << start << "\n";
    cout << "CPU-TIME END " << end << "\n";
    cout << "CPU-TIME END - START " <<  end - start << "\n";
    cout << "TIME(SEC) " << static_cast<double>(end - start) / CLOCKS_PER_SEC << "\n";

    return 0;
}

Umgebung

  • GNU / LINUX (Archlinux)
  • Kernel 3.3
  • GLIBC-2.15, LIBSTDC ++ 4.7 (GCC-LIBS), GCC 4.7, Coreutils 8.16
  • Verwenden von RUNLEVEL 3 (Mehrbenutzer, Netzwerk, Terminal, keine GUI)
  • INTEL SSD-Postville 80 GB, gefüllt bis zu 50%
  • Kopieren Sie eine 270 MB OGG-VIDEO-DATEI

Schritte zum Reproduzieren

 1. $ rm from.ogg
 2. $ reboot                           # kernel and filesystem buffers are in regular
 3. $ (time ./program) &>> report.txt  # executes program, redirects output of program and append to file
 4. $ sha256sum *.ogv                  # checksum
 5. $ rm to.ogg                        # remove copy, but no sync, kernel and fileystem buffers are used
 6. $ (time ./program) &>> report.txt  # executes program, redirects output of program and append to file

Ergebnisse (verwendete CPU-ZEIT)

Program  Description                 UNBUFFERED|BUFFERED
ANSI C   (fread/frwite)                 490,000|260,000  
POSIX    (K&R, read/write)              450,000|230,000  
FSTREAM  (KISS, Streambuffer)           500,000|270,000 
FSTREAM  (Algorithm, copy)              500,000|270,000
FSTREAM  (OWN-BUFFER)                   500,000|340,000  
SENDFILE (native LINUX, sendfile)       410,000|200,000  

Die Dateigröße ändert sich nicht.
sha256sum druckt die gleichen Ergebnisse.
Die Videodatei kann weiterhin abgespielt werden.

Fragen

  • Welche Methode würden Sie bevorzugen?
  • Kennen Sie bessere Lösungen?
  • Sehen Sie Fehler in meinem Code?
  • Kennen Sie einen Grund, eine Lösung zu vermeiden?

  • FSTREAM (KISS, Streambuffer)
    Ich mag dieses wirklich, weil es sehr kurz und einfach ist. Soweit ich weiß, ist der Operator << für rdbuf () überladen und konvertiert nichts. Richtig?

Vielen Dank

Update 1
Ich habe die Quelle in allen Beispielen so geändert, dass das Öffnen und Schließen der Dateideskriptoren in die Messung von clock () einbezogen wird . Es gibt keine weiteren wesentlichen Änderungen im Quellcode. Die Ergebnisse haben sich nicht geändert! Ich habe auch Zeit genutzt , um meine Ergebnisse zu überprüfen.

Update 2
ANSI C-Beispiel geändert: Der Zustand der while-Schleife ruft nicht mehr feof () auf, sondern ich habe fread () in den Zustand verschoben . Es sieht so aus, als würde der Code jetzt 10.000 Uhren schneller laufen.

Messung geändert: Die früheren Ergebnisse wurden immer gepuffert, da ich die alte Befehlszeile rm to.ogv && sync && time ./program für jedes Programm einige Male wiederholt habe . Jetzt starte ich das System für jedes Programm neu. Die ungepufferten Ergebnisse sind neu und zeigen keine Überraschung. Die ungepufferten Ergebnisse haben sich nicht wirklich geändert.

Wenn ich die alte Kopie nicht lösche, reagieren die Programme anders. Das Überschreiben einer vorhandenen gepufferten Datei ist mit POSIX und SENDFILE schneller, alle anderen Programme sind langsamer. Möglicherweise wirken sich die abgeschnittenen oder erstellten Optionen auf dieses Verhalten aus. Das Überschreiben vorhandener Dateien mit derselben Kopie ist jedoch kein realer Anwendungsfall.

Das Durchführen der Kopie mit cp dauert 0,44 Sekunden ungepuffert und 0,30 Sekunden gepuffert. So cp ist ein wenig langsamer als die POSIX - Probe. Sieht gut aus für mich.

Vielleicht füge ich auch Beispiele und Ergebnisse von mmap () und copy_file()von boost :: filesystem hinzu.

Update 3
Ich habe dies auch auf eine Blog-Seite gestellt und ein wenig erweitert. Einschließlich splice () , einer Low-Level-Funktion des Linux-Kernels. Vielleicht folgen weitere Beispiele mit Java. http://www.ttyhoney.com/blog/?page_id=69


5
fstreamist definitiv eine gute Option für Dateioperationen.
Chris


29
Sie haben den faulen Weg vergessen: system ("cp from.ogv to.ogv");
fbafelipe

3
#include <copyfile.h> copyfile(const char *from, const char *to, copyfile_state_t state, copyfile_flags_t flags);
Martin York

3
Es tut mir leid, dass ich so spät eingechippt habe, aber ich würde keines davon als "sicher" bezeichnen, da es keine Fehlerbehandlung gibt.
Richard Kettlewell

Antworten:


259

Kopieren Sie eine Datei auf vernünftige Weise:

#include <fstream>

int main()
{
    std::ifstream  src("from.ogv", std::ios::binary);
    std::ofstream  dst("to.ogv",   std::ios::binary);

    dst << src.rdbuf();
}

Dies ist so einfach und intuitiv zu lesen, dass es die zusätzlichen Kosten wert ist. Wenn wir viel tun, ist es besser, auf Betriebssystemaufrufe im Dateisystem zurückzugreifen. Ich bin sicher, boosthat eine Kopierdatei-Methode in seiner Dateisystemklasse.

Es gibt eine C-Methode für die Interaktion mit dem Dateisystem:

#include <copyfile.h>

int
copyfile(const char *from, const char *to, copyfile_state_t state, copyfile_flags_t flags);

29
copyfileist nicht tragbar; Ich denke, es ist spezifisch für Mac OS X. Es existiert sicherlich nicht unter Linux. boost::filesystem::copy_fileist wahrscheinlich die portabelste Methode, um eine Datei über das native Dateisystem zu kopieren.
Mike Seymour

4
@ MikeSeymour: copyfile () scheint eine BSD-Erweiterung zu sein.
Martin York

10
@ duedl0r: Nein. Objekte haben Destruktoren. Der Destruktor für Streams ruft automatisch close () auf. codereview.stackexchange.com/q/540/507
Martin York

11
@ duedl0r: Ja. Aber das ist so, als würde man sagen "wenn die Sonne untergeht". Sie können sehr schnell nach Westen laufen und Ihren Tag etwas verlängern, aber die Sonne geht unter. Es sei denn, Sie haben Fehler und Speicherverlust (es wird außerhalb des Gültigkeitsbereichs). Da es hier jedoch keine dynamische Speicherverwaltung gibt, kann es kein Leck geben und sie werden außer Reichweite geraten (genau wie die Sonne untergeht).
Martin York

6
Dann wickeln Sie es einfach in einen {} Scope Block
Paulm

62

In C ++ 17 besteht die Standardmethode zum Kopieren einer Datei darin, den <filesystem>Header einzuschließen und Folgendes zu verwenden:

bool copy_file( const std::filesystem::path& from,
                const std::filesystem::path& to);

bool copy_file( const std::filesystem::path& from,
                const std::filesystem::path& to,
                std::filesystem::copy_options options);

Die erste Form entspricht der zweiten Form, die copy_options::noneals Optionen verwendet wird (siehe auch copy_file).

Die filesystemBibliothek wurde ursprünglich als entwickelt boost.filesystemund schließlich ab C ++ 17 mit ISO C ++ zusammengeführt.


2
Warum gibt es keine einzige Funktion mit einem Standardargument wie bool copy_file( const std::filesystem::path& from, const std::filesystem::path& to, std::filesystem::copy_options options = std::filesystem::copy_options::none);?
Jepessen

2
@Jepessen Da bin ich mir nicht sicher. Vielleicht spielt es keine Rolle .
Manlio

@Jepessen In der Standardbibliothek ist sauberer Code von größter Bedeutung. Überladungen (im Gegensatz zu einer Funktion mit Standardparametern) machen die Absicht des Programmierers klarer.
März 2377

@Peter Dies sollte nun wahrscheinlich die akzeptierte Antwort sein, da C ++ 17 verfügbar ist.
Martin York

21

Zu viele!

Der Wegpuffer "ANSI C" ist redundant, da a FILEbereits gepuffert ist. (Die Größe dieses internen Puffers ist das, was BUFSIZtatsächlich definiert.)

Der "OWN-BUFFER-C ++ - WAY" wird im Laufe der Zeit langsam sein fstream, was viel virtuelles Dispatching bewirkt und wiederum interne Puffer oder jedes Stream-Objekt verwaltet. (Das "COPY-ALGORITHM-C ++ - WAY" leidet nicht darunter, da die streambuf_iteratorKlasse die Stream-Schicht umgeht.)

Ich bevorzuge "COPY-ALGORITHM-C ++ - WAY", aber ohne eine zu fstreamerstellen, erstelle einfach nackte std::filebufInstanzen, wenn keine tatsächliche Formatierung erforderlich ist.

Für eine rohe Leistung können Sie POSIX-Dateideskriptoren nicht übertreffen. Es ist hässlich, aber tragbar und auf jeder Plattform schnell.

Der Linux-Weg scheint unglaublich schnell zu sein - vielleicht hat das Betriebssystem die Funktion zurückkehren lassen, bevor die E / A beendet wurde? In jedem Fall ist das für viele Anwendungen nicht portabel genug.

BEARBEITEN : Ah, "natives Linux" kann die Leistung verbessern, indem Lese- und Schreibvorgänge mit asynchronen E / A verschachtelt werden. Das Anhäufen von Befehlen kann dem Festplattentreiber helfen, zu entscheiden, wann am besten gesucht werden soll. Sie können Boost Asio oder pthreads zum Vergleich ausprobieren. Was "POSIX-Dateideskriptoren nicht übertreffen" betrifft ... nun, das stimmt, wenn Sie etwas mit den Daten tun, nicht nur blind kopieren.


ANSI C: Aber ich muss der Funktion fread / fwrite eine Größe geben? pubs.opengroup.org/onlinepubs/9699919799/toc.htm
Peter

@PeterWeber Nun ja, es ist wahr, dass BUFSIZ einen so guten Wert wie jeder andere hat und wahrscheinlich die Dinge relativ zu einem oder "nur wenigen" Zeichen gleichzeitig beschleunigen wird. Wie auch immer, die Leistungsmessung zeigt, dass es auf keinen Fall die beste Methode ist.
Potatoswatter

1
Ich habe kein tiefes Verständnis dafür, daher sollte ich mit Annahmen und Meinungen vorsichtig sein. Linux-Way läuft im Kernelspace afaik. Dies sollte eine langsame Kontextumschaltung zwischen Kernelspace und Userspace vermeiden. Morgen werde ich noch einmal einen Blick auf die Manpage von sendfile werfen. Vor einiger Zeit sagte Linus Torvalds, er mag Userspace-Filesystems nicht für schwere Jobs. Vielleicht ist sendfile ein positives Beispiel für seine Ansicht?
Peter

5
" sendfile()Kopiert Daten zwischen einem Dateideskriptor und einem anderen. Da dieses Kopieren innerhalb des Kernels erfolgt, sendfile()ist es effizienter als die Kombination von read(2)und write(2), die das Übertragen von Daten zum und vom Benutzerbereich erfordern würde.": kernel.org/doc/man-pages /online/pages/man2/sendfile.2.html
Max Lybbert

1
Könnten Sie ein Beispiel für die Verwendung von filebufRohobjekten veröffentlichen?
Kerrek SB

14

Ich möchte das sehr machen wichtig zu beachten , dass die Linux - Methode sendfile () in ein großes Problem hat , dass es keine Dateien kopieren kann mehr als 2 GB groß! Ich hatte es nach dieser Frage implementiert und hatte Probleme, weil ich damit HDF5-Dateien mit einer Größe von vielen GB kopierte.

http://man7.org/linux/man-pages/man2/sendfile.2.html

sendfile () überträgt höchstens 0x7ffff000 (2.147.479.552) Bytes und gibt die Anzahl der tatsächlich übertragenen Bytes zurück. (Dies gilt sowohl für 32-Bit- als auch für 64-Bit-Systeme.)


1
hat sendfile64 () das gleiche Problem?
Graywolf

1
@Paladin Es scheint, dass sendfile64 entwickelt wurde, um diese Einschränkung zu umgehen. In der Manpage: "" "Der ursprüngliche Linux-Systemaufruf sendfile () war nicht für die Verarbeitung großer Datei-Offsets ausgelegt. Folglich fügte Linux 2.4 sendfile64 () mit einem breiteren Typ für das Offset-Argument hinzu. Die Wrapper-Funktion glibc sendfile () geht transparent mit den
Kernelunterschieden um

sendfile64 hat anscheinend das gleiche Problem . Die Verwendung des Offset-Typs off64_termöglicht es jedoch, eine Schleife zu verwenden, um große Dateien zu kopieren, wie in einer Antwort auf die verknüpfte Frage gezeigt.
PCworld

Dies ist in man wirtten: 'Beachten Sie, dass ein erfolgreicher Aufruf von sendfile () möglicherweise weniger Bytes als angefordert schreiben kann; Der Anrufer sollte bereit sein, den Anruf erneut zu versuchen, wenn nicht gesendete Bytes vorhanden sind. ' sendfile oder sendfile64 müssen möglicherweise innerhalb einer Schleife aufgerufen werden, bis die vollständige Kopie abgeschlossen ist.
Philip Lhardy

2

Qt hat eine Methode zum Kopieren von Dateien:

#include <QFile>
QFile::copy("originalFile.example","copiedFile.example");

Beachten Sie, dass Sie Qt installieren müssen (Anweisungen hier ) und es in Ihr Projekt aufnehmen müssen (wenn Sie Windows verwenden und kein Administrator sind, können Sie Qt stattdessen hier herunterladen ). Siehe auch diese Antwort .


1
QFile::copyist aufgrund seiner 4k-Pufferung lächerlich langsam .
Nicolas Holthaus

1
Die Langsamkeit wurde in neueren Versionen von behoben Qt. Ich benutze 5.9.2und die Geschwindigkeit ist auf dem Niveau der nativen Implementierung. Übrigens. Wenn man sich den Quellcode ansieht, scheint Qt tatsächlich die native Implementierung aufzurufen.
VK

1

Für diejenigen, die Boost mögen:

boost::filesystem::path mySourcePath("foo.bar");
boost::filesystem::path myTargetPath("bar.foo");

// Variant 1: Overwrite existing
boost::filesystem::copy_file(mySourcePath, myTargetPath, boost::filesystem::copy_option::overwrite_if_exists);

// Variant 2: Fail if exists
boost::filesystem::copy_file(mySourcePath, myTargetPath, boost::filesystem::copy_option::fail_if_exists);

Beachten Sie, dass boost :: filesystem :: path auch als wpath für Unicode verfügbar ist . Und das könnte man auch gebrauchen

using namespace boost::filesystem

wenn Sie diese langen Typnamen nicht mögen


Die Dateisystembibliothek von Boost ist eine der Ausnahmen, für die eine Kompilierung erforderlich ist. Nur zur Info!
SimonC

0

Ich bin mir nicht ganz sicher, was eine "gute Art" ist, eine Datei zu kopieren, aber wenn "gut" "schnell" bedeutet, könnte ich das Thema ein wenig erweitern.

Aktuelle Betriebssysteme wurden seit langem für die Ausführung der Kopie der Mill-Datei optimiert. Kein kluger Code wird das übertreffen. Es ist möglich, dass sich einige Varianten Ihrer Kopiertechniken in einigen Testszenarien als schneller erweisen, in anderen Fällen jedoch höchstwahrscheinlich schlechter.

In der Regel sendfilekehrt die Funktion wahrscheinlich zurück, bevor der Schreibvorgang festgeschrieben wurde, wodurch der Eindruck entsteht, schneller als die anderen zu sein. Ich habe den Code nicht gelesen, aber es liegt mit Sicherheit daran, dass er einen eigenen dedizierten Puffer zuweist, der Speicher gegen Zeit eintauscht. Und der Grund, warum es bei Dateien mit mehr als 2 GB nicht funktioniert.

Solange Sie mit einer kleinen Anzahl von Dateien arbeiten, geschieht alles in verschiedenen Puffern (die C ++ - Laufzeit ist die erste, wenn Sie sie verwenden iostream, die internen des Betriebssystems, anscheinend ein zusätzlicher Puffer in Dateigröße im Fall von sendfile). Auf tatsächliche Speichermedien wird erst zugegriffen, wenn genügend Daten verschoben wurden, um das Drehen einer Festplatte zu lohnen.

Ich nehme an, Sie könnten die Leistung in bestimmten Fällen leicht verbessern. Aus dem Kopf:

  • Wenn Sie eine große Datei auf dieselbe Festplatte kopieren, kann die Verwendung eines Puffers, der größer als der des Betriebssystems ist, die Dinge ein wenig verbessern (aber wir sprechen hier wahrscheinlich von Gigabyte).
  • Wenn Sie dieselbe Datei an zwei verschiedenen physischen Zielen kopieren möchten, werden Sie die drei Dateien wahrscheinlich schneller gleichzeitig öffnen als zwei copy_filenacheinander aufrufen (obwohl Sie den Unterschied kaum bemerken werden, solange die Datei in den Betriebssystem-Cache passt).
  • Wenn Sie mit vielen kleinen Dateien auf einer Festplatte arbeiten, möchten Sie diese möglicherweise stapelweise lesen, um die Suchzeit zu minimieren (obwohl das Betriebssystem bereits Verzeichniseinträge zwischenspeichert, um zu vermeiden, dass nach verrückten und winzigen Dateien gesucht wird, wird die Festplattenbandbreite wahrscheinlich ohnehin drastisch reduziert).

All dies liegt jedoch außerhalb des Bereichs einer universellen Dateikopierfunktion.

Meiner Meinung nach sollte eine C ++ - Dateikopie nur die file_copydedizierte C ++ 17- Funktion verwenden, es sei denn, es ist mehr über den Kontext bekannt, in dem die Dateikopie erfolgt, und es können einige clevere Strategien entwickelt werden, um das Betriebssystem zu überlisten.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.