Od jednego z czytelników (pozdrowienia dla Jakuba) dostałem pytanie odnośnie samego początku rozdziału „Synchronizacja” (rozdział 9 mojej książki), gdzie to podałem przykładowy kod w Pythonie, który sprawdza czy plik istnieje, a jeśli nie, to go tworzy. W książce był to w zasadzie „anty-przykład” - kod ten jest dość oczywiście podatny na race-condition (sytuację wyścigu), ponieważ sprawdzenie czy plik istnieje i jego utworzenie w tym wypadku to dwie oddzielne operacje, a więc taki plik mógłby w tzw. międzyczasie powstać (tj. istnieje pewne okno czasu, w którym możliwe jest utworzenie pliku). W zależności od przypadku może to być zupełnie nieistotne sytuacja lub poważny błąd bezpieczeństwa pozwalający na podniesienie uprawnień. Czytelnik wskazał jednak, że nigdzie (w okolicy) nie napisałem jak to powinno być poprawnie zrobione - stąd ten post.

Na początek kod z książki:

fname = "/writable/file/path/example_file"
if not os.path.isfile(fname):
 f = open(fname, "w")

I teraz tak: zacznę od tego, że w zasadzie książce napisałem jak to zrobić poprawnie na stronie 353 w rozdziale o systemie plików (rozdział 10). Ale przykład dotyczył C i uprawnień do plików, i w sumie nigdzie w rozdziale o synchronizacji nie wspominam, żeby rzucić na niego okiem - mój błąd.

(Tak na marginesie, to ten kod i tak by się wysypał jeśli istniałby katalog o takiej nazwie; isfile wtedy zwróci False, open spróbuje utworzyć plik, ale to się nie powiedzie. Tak btw, z mojej pracy przy analizie malware pamiętam, że jedna ze sztuczek, która używałem, żeby malware nie tworzył ponownie plików w danym miejscu, było właśnie utworzenie katalogu o takiej samej nazwie - pliki malware usuwał, ale do usunięcia katalogu często trzeba użyć innego API, więc malware się wykładał - taka tam sztuczka, którą warto czasem pamiętać)

Konkretniej, napisałem tam, że trzeba obie operacje sprowadzić do jednej atomowej (tj. nie tyle chodzi o zmniejszenie okna czasu pomiędzy operacjami, co o całkowite jego usunięcie). W przypadku tworzenia plików trzeba o coś takiego poprosić system operacyjny (a konkretniej: jądro systemu lub API systemowe, które i tak poprosi jądro systemu) o "utworzenie pliku tylko jeśli ten nie istnieje".

W przypadku systemów GNU/Linux służy do tego syscall/funkcja open z parametrami:

O_CREAT - utworzenie pliku, jeśli ten nie istnieje.
O_EXCL - upewnienie się, że plik naprawdę zostanie utworzony (jeśli już istnieje, open zwróci błąd).

Na przykład, cytując kod w języku C ze strony 353:

 int fd = open("/tmp/knownname",
               O_CREAT | // Utwórz nowy plik, jeśli nie istnieje.
               O_EXCL | // Upewnij się, że na pewno zostanie
                        // utworzony nowy plik (tj. plik nie istniał
                        // wcześniej).
               O_WRONLY, // Otwarcie tylko do zapisu.
               0600 // S_IRUSR | S_IWUSR - czyli rw-------
               );
 if (fd == -1) {
   perror("Failed to create file");
   return 1;
 }

Jeśli chodzi o to jak to zrobić w Pythonie, to w zasadzie trzeba skorzystać z tego samego API (działa zarówno pod Windowsem jak i pod Linuxem, chociaż szczerze, to nie sprawdzałem dokładnie jak pod Windowsem jest zaimplementowane), a następnie posiadając deskryptor w postaci numerycznej, utworzyć na jego podstawie obiekt typu file znany z Pythona. Realizuje się to w następujący sposób:

>>> import os, io
>>> d = os.open("/tmp/thisfiledoesnotexist", os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600)
>>> f = io.FileIO(d, 'w')
>>> f.write('ala ma kota')
11L
>>> f.close()
>>> print open("/tmp/thisfiledoesnotexist").read()
ala ma kota
>>>

Jeśli plik by już istniał, to os.open rzuci wyjątek:

• Python 2.7: OSError: [Errno 17] File exists: ...
• Python 3.4: FileExistsError: [Errno 17] File exists: ...

I tyle.

P.S. W końcu jakiś post techniczny! YAY! :)
P.S.2. Pewnie co jakiś czas będę wrzucać posty bazujące na pytaniach o to co tam w książce napisałem a było w jakiś sposób niejasne/niekompletne - trochę takich pytań dostałem, więc materiał jest.

Comments:

2016-03-10 10:06:50 = py123
{
Tak przy okazji, w operacji na plikach warto używać context managera.
}
2016-03-10 10:21:16 = Qba
{
Dla pythona 3.4 chyba powinno być:
f.write(b'ala ma kota')
}
2016-03-10 11:03:43 = Gynvael Coldwind
{
@py123
Ogólnie zgoda :)
Warto dodać, że są przypadki kiedy plik musi zostać otwarty dłużej - wtedy context manager raczej się nie przyda.

@Qba
Masz rację :)
Testowałem f.write tylko na 2.7 i przyznaję, że założyłem, że FileIO w przypadku "w" zachowa się jak open w przypadku "w", tj. zostanie utworzony plik "tekstowy" (a więc tym str będzie akceptowany i automagicznie kodowany domyślnym kodowaniem, w przeciwieństwie do "wb").
}

Add a comment:

Nick:
URL (optional):
Math captcha: 6 ∗ 7 + 8 =