Hypercell ein ] Hypercell aus ] Zeige Navigation ] Verstecke Navigation ]
c++.net  
   

Die mobilen Seiten von c++.net:
https://m.c-plusplus.net

  
C++ Forum :: Linux/Unix ::  [Linux] Vorhandene Datei verändern (atomisch!)     Zeige alle Beiträge auf einer Seite Auf Beitrag antworten
Autor Nachricht
Biolunar
Moderator

Benutzerprofil
Anmeldungsdatum: 16.02.2010
Beiträge: 510
Beitrag Biolunar Moderator 16:56:01 26.06.2017   Titel:   [Linux] Vorhandene Datei verändern (atomisch!)            Zitieren

Ich bekomme von der Kommandozeile einige Dateinamen und möchte diese Dateien verändern ohne sie zu korruptieren, falls irgendwas schiefläuft. Daher meine Idee:
  • Temporäre Datei erstellen
  • Die zu ändernde Datei einlesen
  • Alle Änderungen der Datei in die temp. Datei schreiben
  • temp. Datei zu der alten Datei umbenennen
Soweit die Theorie :)

C:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

#include <sys/types.h>

#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <linux/limits.h>
#include <linux/fs.h>
 
static void write_all(int const fd, void const* const buf, size_t const count)
{
    char const* const dest = buf;
    size_t written = 0;
    while (written != count)
    {
        ssize_t const ret = write(fd, dest + written, count - written);
        if (ret == -1)
        {
            if (errno == EINTR)
                continue;
 
            perror("write");
            exit(EXIT_FAILURE);
        }
 
        written += (size_t)ret;
    }
}
 
static void test_rename(char const* const filename)
{
    int const fd1 = open(filename, O_RDONLY, 0);
    if (fd1 == -1)
    {
        perror("open 1");
        exit(EXIT_FAILURE);
    }
    int const fd2 = open("/tmp", O_RDWR | O_TMPFILE, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
    if (fd2 == -1)
    {
        perror("open 2");
        exit(EXIT_FAILURE);
    }
 
    // TODO: Analyze fd1 and write the important stuff into fd2.
    char const buf[] = "test\n";
    write_all(fd2, buf, sizeof buf);
 
    char path[PATH_MAX] = {0};
    snprintf(path, sizeof path, "/proc/self/fd/%d", fd2);
    printf("path: %s\n", path);
    //if (syscall(SYS_renameat2, AT_FDCWD, path, AT_FDCWD, filename, RENAME_EXCHANGE) < 0)
    if (renameat(AT_FDCWD, path, AT_FDCWD, filename) == -1)
    {
        printf("error: %d\n", errno);
        perror("renameat");
        exit(EXIT_FAILURE);
    }
 
    close(fd2);
    close(fd1);
}
 
int main(int argc, char* argv[])
{
    test_rename("file");
 
    return EXIT_SUCCESS;
}

Das Problem ist, dass /tmp nicht notwendigerweise auf der selben Partition liegt. Bei mir ist es z.B. ein tempfs und somit bricht renameat in Zeile 56 mit EXDEV als Fehler ab. Ergibt für mich Sinn. Mit renameat2 in Zeile 55 habe ich es auch probiert; selber Fehler.
Okay. Ich sollte die temp. Datei also im selben Verzeichnis wie die erste Datei erstellen. Nur wie mache ich das geschickt? Mit
C:
int const fd2 = openat(fd1, ".", O_RDWR | O_TMPFILE, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
bemängelt es, dass es sich nicht um ein Verzeichnis handelt.

Meine Frage ist also: wie erstelle ich eine temporäre Datei in dem selben Verzeichnis wie die erste Datei?
Eine Analyse des Pfades im filename String würde ich auch nur ungern machen, keine Ahnung auf welche Sonderheiten ich alles Acht geben muss. Im Codebeispiel ist "file" ja auch relativ; da müsste ich ja am String herumdoktoren, damit open(at) nicht fehlschlägt.
Das snprintf in Zeile 53 gefällt mir auch nicht. Kann ich mein Vorhaben auch irgendwie nur mit den file descriptoren erreichen? Ich möchte bei dem ganzen am Ende ohne libc auskommen und nur direkt die Syscalls verwenden. sendfile(2) würde hier ja funktionieren, sogar wenn die temp. Datei in /tmp liegt. Allerdings würde ich dann insgesamt ja zwei mal schreiben, was nicht akzeptabel ist.
camper
Moderator

Benutzerprofil
Anmeldungsdatum: 06.08.2004
Beiträge: 7127
Beitrag camper Moderator 17:30:23 26.06.2017   Titel:              Zitieren

mktemp ?
Biolunar
Moderator

Benutzerprofil
Anmeldungsdatum: 16.02.2010
Beiträge: 510
Beitrag Biolunar Moderator 18:17:24 26.06.2017   Titel:              Zitieren

mktemp ist furchtbar. Eine race condition zwischen dem Rückgabewert und dem Erstellen der temporären Datei ist hier unvermeidbar!
mkstemp ist schon ein Schritt in die richtige Richtung, mich würde aber eine Lösung nur mit Syscalls interessieren. Wenn ich eine Reihe von Dateien bearbeite und SIGKILL gesendet bekomme, soll auch kein Überrest im Dateisystem bestehenbleiben, was sich mit mkstemp aber nicht vermeiden lässt. Das O_TMPFILE flag von open ist hier sehr nützlich.

Ich neige fast schon dazu den Pfad der alten Datei zu analysieren um das konkrete Verzeichnis zu erhalten. So viele Sonderfälle gibt es ja gar nicht:
Absolute Pfade und relative Pfade (mit '/') kann ich beim letzen '/' abtrennen. Bei relativen Pfaden (ohne '/') nehme ich das current working directory.
lagalopex
Mitglied

Benutzerprofil
Anmeldungsdatum: 21.01.2008
Beiträge: 450
Beitrag lagalopex Mitglied 15:01:33 27.06.2017   Titel:              Zitieren

Etwas googlen zeigt, dass es wohl (noch) nicht möglich ist. O_TMPFILE wurde zu unbedacht implementiert, so dass sich der effektive Nutzen stark in Grenzen hält. Auch dieser Umweg über /proc/self/fd/... sieht sehr unüberlegt aus.

Zu deinem Bisherigen:
fd1 ist eine Datei, kein Verzeichnis. Daher der Fehler bzgl "kein Verzeichnis". (Mit einem Deskriptor von open(dirname(filename)... geht es.)

/proc/self/fd/... ist ein Symlink. Für linkat kann man angeben, dass dereferenziert werden soll. Bei renameat geht das nicht. (Daher würde der Symlink umbenannt werden, was wohl fehlschlägt, da /proc/ im Allgemeinen ein eigener Mountpoint ist.)

Für linkat wird AT_EMPTY_PATH angeboten um Dateideskriptoren ohne Pfad zu verwenden. Das klappt aber nur mit entsprechenden Rechten (root bzw CAP_DAC_READ_SEARCH), da damit Dateisystemrechte umgangen werden.


Wenn du alles in einem O_TMPFILE vorbereitest, dann ist die Zeitspanne für linkat mit einem temp. Namen gefolgt von renameat sehr klein. (Nicht sauber, nicht elegant, ...)


Wenn es eine bessere (oder gar gute ;) ) Lösung gibt, würde es mich auch interessieren!
Biolunar
Moderator

Benutzerprofil
Anmeldungsdatum: 16.02.2010
Beiträge: 510
Beitrag Biolunar Moderator 15:02:52 28.06.2017   Titel:              Zitieren

Ahh ich habe die Funktionsweise von openat missverstanden! Ich nahm an, dass der erste Parameter ein file descriptor einer Datei ist und in Abhängigkeit dieser das Verzeichnis bestimmt wird. Richtig ist: der file descriptor soll dieses Verzeichnis darstellen.

Mein zweiter Fehler: ob die temp. Datei nun in /tmp oder in "." liegt spielt keine Rolle, sondern renameat kann keine symlinks umbenennen. Dafür braucht man linkat.

Folglich muss ich also alle mir möglichen Signale blocken, ein linkat gefolgt von einem renameat machen und wieder alle Signale zulassen. Nur bei einem SIGKILL, das zwischen dem linkat und renameat auftritt würde dann eine temporäre Datei im Dateisystem zurück bleiben. Ich glaube damit kann ich leben ;-)
Nachteil ist: ich muss selbst einen einzigartigen Namen für die temp. Datei finden, damit keine Kollisionen auftreten.

Oder ich mache mir das Leben einfach und verwende mkstemp. Nachteil hierbei ist, dass die temp. Datei länger als nötig im Dateisystem hängt, man kaum Kontrolle über den Namen dieser temp. Datei hat und dass nicht garantiert ist, dass auch ein einzigartiger Name gefunden wird.
C++ Forum :: Linux/Unix ::  [Linux] Vorhandene Datei verändern (atomisch!)   Auf Beitrag antworten

Zeige alle Beiträge auf einer Seite




Nächstes Thema anzeigen
Vorheriges Thema anzeigen
Sie können Beiträge in dieses Forum schreiben.
Sie können auf Beiträge in diesem Forum antworten.
Sie können Ihre Beiträge in diesem Forum nicht bearbeiten.
Sie können Ihre Beiträge in diesem Forum nicht löschen.
Sie können an Umfragen in diesem Forum nicht mitmachen.

Powered by phpBB © 2001, 2002 phpBB Group :: FI Theme

c++.net ist Teilnehmer des Partnerprogramms von Amazon Europe S.à.r.l. und Partner des Werbeprogramms, das zur Bereitstellung eines Mediums für Websites konzipiert wurde, mittels dessen durch die Platzierung von Werbeanzeigen und Links zu amazon.de Werbekostenerstattung verdient werden kann.

Die Vervielfältigung der auf den Seiten www.c-plusplus.de, www.c-plusplus.info und www.c-plusplus.net enthaltenen Informationen ohne eine schriftliche Genehmigung des Seitenbetreibers ist untersagt (vgl. §4 Urheberrechtsgesetz). Die Nutzung und Änderung der vorgestellten Strukturen und Verfahren in privaten und kommerziellen Softwareanwendungen ist ausdrücklich erlaubt, soweit keine Rechte Dritter verletzt werden. Der Seitenbetreiber übernimmt keine Gewähr für die Funktion einzelner Beiträge oder Programmfragmente, insbesondere übernimmt er keine Haftung für eventuelle aus dem Gebrauch entstehenden Folgeschäden.