Lab Zadanie 2

Treść zadania: http://students.mimuw.edu.pl/SO/PUBLIC-SO/2009-10/Zadania/zadanie2.txt

Uwagi do treści zadania

  • (MK) Podane w treści zadania ograniczenie O(min(N,M) jest chyba bez sensu - chodzi raczej o to, żeby w ramach kopii nie trzymać całego pliku, tylko np. diff z wersją roota?
  • (MK) Żeby użytkownik mógł pracować na swojej kopii, trzeba mu ją na żądanie utworzyć z danych: "wersja r00ta + dane o prywatnej kopii (np. diff)"; pytanie, czy należy odtwarzać tylko tyle bajtów kopii, ile proces chce przeczytać/zapisać, czy też można dla wygody od razu utworzyć całą wersję? (w tym drugim wypadku jest prościej, natomiast taka tymczasowa, niezapisana wersja robocza może zajmować tyle, co oryginał, więc nie jest zachowane ograniczene O(N,M); pytanie, czy ograniczenie to dotyczy też plików tymczasowych, czy tylko plików, które zostają trwale w systemie?)
    • (MK) Dostałem od Kuby Łąckiego odpowiedź, że tymczasowe kopie też trzeba przechowywać oszczędnie (tj. odtwarzać tyle bajtów, ile user chce zapisać/odczytać)
  • (MP) Czy brak truncate oznacza, że kopia nie będzie mniejsza niż plik roota i plik będzie tylko przyrastał, czy to jest bardziej skomplikowane?

Tutoriale do FUSE

http://sourceforge.net/apps/mediawiki/fuse/index.php?title=SimpleFilesystemHowto (system plików w Pythonie)

Uwagi do FUSE

  • żeby łatwo debugować, nalepiej odpalać FUSE z opcją -d
  • żeby z systemu plików mógł korzystać użytkownik inny od tego, który system zamontował, trzeba zamontować system z opcją allow_other (np. nazwa_programu mountdir -o allow_other)) i w pliku /etc/fuse.conf odkomentować odpowiednią linijkę
  • (MK) okazuje się, że FUSE działa raczej niezgodnie z semantyką naszego systemu plików. Mianowicie, w momencie, kiedy próbuje my usunąć (unlink) otwarty plik, FUSE zamiast unlinka wywołuje rename i próbuje zmienić nazwę tego pliku na coś typu ".fusehiddenXXXXXXXXX". Następne odwołania do tego pliku, np. release, będą wykonywane z użyciem tej nowej nazwy (np. przy sekwencji open-unlink-release open wykonany na pliku "foo" poskutkuje releasem na pliku ".fusehidden[cyferki]"). Co więcej, unlink następuje dopiero po wszystkich releasach (czyli gdy plik nie jest już otwarty przez nikogo, kto miał go otwarty przed usunięciem) i wykonywany jest nie dość, że na pliku ".fusehidden[cyferki]", to jeszcze przez roota (tzn. uid jest przy tym wywołaniu ustawiony na 0).

Np. przy takiej sytuacji:
(proces 1) open "foo"
(proces 2) rm "foo"
(proces 3) open "foo"

unlink związany z rm zostanie zastąpiony renamem. Faktyczny unlink dokona się, gdy proces 1 zamknie plik. W międzyczasie proces 3 może go sobie normalnie otworzyć pod nazwą foo.

Prosty test, żeby na własne oczy zobaczyć, że tak się dzieje - odpalcie sobie jeden proces, który otwiera plik i sobie czeka, a w międzyczasie niech drugi proces usunie plik.

Oczywiście z punktu widzenia semantyki naszego systemu jest to bez sensu. Ktoś ma pomysł na sensowne rozwiązanie? Ja wymyśliłem, żeby tworzyć sobie gdzieś tam ten plik o nazwie ".fusehiddenXXXXXXXXX" i trzymać w nim "prawdziwą" ścieżkę do pliku oraz uid tego, kto chciał wykonać unlink (żeby przy faktycznym unlinku można było zmienić uid z roota na właściwy). Przy każdej operacji sprawdzamy, czy nazwa argumentu nie zaczyna się czasem do ".fusehidden", a jeśli tak, to czytamy z tego pliku faktyczne dane i je zamieniamy.

  • (KM): Działa dopóki użytkownik nie stworzy pliku o nazwie ".fuse_hidden…" (jeśli się nie mylę, to jest tam podkreślnik). Nie podoba mi się to, jak to jest rozwiązane w fuse (żeby to chociaż było udokumentowane). Znalazł to ktoś z Was w dokumentacji?
    • (MK) w szczególności, jeśli użytkownik nie może tworzyć plików o tej nazwie, nie można odpalić jednego systemu plików psp w drugim (tj. zamontować jeden system w katalogu B z katalogiem C jako katalogiem danych, a następnie drugi zamontować w A z katalogiem B jako danymi) ;)
  • (BG): Zgadzam się. Może jest jakaś opcja, żeby to wyłączyć? A tak swoją drogą, chyba nie musimy implementować operacji rename? Co, jeżeli w czasie tego oszukanego unlink, rename zwróci błąd?
    • (MK) jeśli nie zaimplementujesz rename, nie będziesz w stanie radzić sobie z funkcjami, które jako argument dostaną ".fuse_hidenXXXX" (np. release w momencie zamykania usuniętego pliku)
    • (BG) To pozostaje każdemu plikowi przypisywać unikalny numer (jak inode) i dane usuwać dopiero kiedy dla każdego open() dojdzie do release()
    • (MK) Nie bardzo rozumiem, w czym to pomaga. To, że trzeba dane "na twardo" usuwać dopiero, gdy każde open zostanie zakończone swoim release, jest oczywiste, ale co ma do tego unikalny numer? Problem jest tutaj z zamienianiem nazw plików na ".fuse_hidden". W normalnym systemie mógłbyś zmienić po prostu nazwę i od tej pory plik nie byłby już dla nikogo widoczny pod starą nazwą (bo przecież został usunięty). Jednak u nas usuwanie ma taką semantykę, że po usunięciu plik może być jednocześnie widziany przez jedne procesy pod oryginalną nazwą ("foo"), a przez inne (te, które miały plik otwarty przed usunięciem) pod FUSE'owym kryptonimem (".fusehiddenXXXXX"). I w jakiś sposób musisz wiedzieć, że te dwie nazwy odnoszą się do tego samego pliku.
    • (MK) Ewentualnie, można sobie te nazwy "hidden" trzymać w jakimś słowniku, który przyporządkowuje im prawdziwe nazwy. Ale napisanie tego zajmie trochę czasu (w Pythonie byłaby to jedna linijka…)
    • (BG) Mam na myśli wypełnianie pola fuse_file_info::fh w implementacji open(), a we wszystkich pozostałych operacjach ignorowanie argumentu path
    • (MK) Nie zadziała przy getattr i unlink (nie dostajemy tam file_info). Poza tym, co byś chciał w tym fh trzymać? To jest pole numeryczne, a my do operacji potrzebujemy ścieżek (np. przy read/write).
    • (BG) Trzeba utrzymywać słownik [nazwa pliku => numer pliku]. open(), getattr() potrzebują wyciągnąć ten numer, unlink() usuwa skojarzenie ze słownika, unlink() i release() usuwają dane, jeżeli nie ma już skojarzenia, ani otwartych plików.
  • (MK) Tutaj jest kawałek z dokumentacji - patrz opis opcji "hard_remove": http://www.sourcefiles.org/Miscellaneous/fuse-2.5.3.tar.gz.shtml
  • (KM) Moje pierwotne rozwiązanie polegało na tym, że unlink _może_ się wykonać na otwartym pliku. To co zamierzam zrobić, to po prostu w rename zrobić wywołanie unlink. Jeśli dobrze zrozumiałem, to po ostatnim zamknięciu takiego usuniętego pliku system spróbuje usunąć pewien plik ukryty (który nie będzie istniał) i to mu się nie uda, ale użytkownik raczej tego nie zauważy (bo kiedy?).
  • (MK) To wydaje się dobrą opcją. Czy jednak nie będzie problemu z wywołaniami read/write/getattr procesu, który miał ten plik otwarty? Jeśli dostają one jako scieżkę ten "fuse_hidden" (nie sprawdzałem tego), to wciąż trzeba sobie radzić z odtworzeniem prawdziwej nazwy ścieżkowej - albo jakoś korzystać z pola fh w celu uzyskania dostępu do właściwego katalogu z wersjami tego pliku
  • (BG) Na razie w main() korzystam z fuse_main() i podaję mu spreparowane przeze mnie argumenty. Jednak żeby użyć allow_other jako użytkownik, musi być odpowiednia konfiguracja w /etc/fuse.conf Czy to jakoś obchodzicie samemu implementując fuse_main(), czy to jest feature fuse?

Testy

  • (MK) jeśli ktoś z Was ma jakieś dobre testy - niech się podzieli!
  • (KM) Testowanie jest bardzo problematyczne wg mnie — np. polecenia unixowe typu mkdir, rm, itp. nie tylko wywołują odpowiednią funkcję systemową, ale też sprawdzają różne rzeczy, np. wywołują getattr (stat).
  • (BG) Należy też pamiętać, że funkcje przekazywane do fuse nie są implementacją syscalli i np. przed open getattr jest wykonywane przez fuse (trochę o tym: http://fuse.sourceforge.net/wiki.old/index.php/FuseInvariants) — (KM) ciekawa uwaga, ale czy to jest aktualne? To może wyjaśniać czemu getattr jest tak często wywoływane.

Współbieżność

  • (MK) Czy ktoś z Was zastanawiał się, jak prosto rozwiązać współbieżność? Problem, z jakim mamy do czynienia, jest typu "czytelnicy i pisarze", tzn.:
    • jeśli root zapisuje do pliku, to nikt inny nie może do niego zapisywać ani z niego czytać
    • jeśli user czyta/pisze do swojej prywatnej kopii, to root nie może do niej zapisywać, ale użytkownicy i root mogą czytać, każdy ze swojej wersji
  • (MK) najprostsztym rozwiązaniem wydaje mi się trzymanie dla każdego pliku pliku blokady; użytkownicy zakładają sobie na niego blokadę współdzieloną, a root wyłączną
  • (MK) bardziej problematyczna jest współbieżność związana z tworzeniem plików (co się dzieje, gdy dwóch użytkowników chce stworzyć całkowicie świeży plik o tej samej nazwie?)
    • (BG) Wcale nie - wystarczy zakładać blokadę na katalogu, w którym jest tworzony plik. Jeżeli już istnieje, to tylko dodać odpowiednią prywatną kopię
    • (MK) tak, tylko taka bloakda uniemożliwia współbieżne wykonywanie operacji na różnych plikach w tym samym katalogu
    • (BG) No to pozostaje korzystać z innych metod wykluczania (O_CREAT|O_EXCL albo mkdir)

FAQ

  • (MS) Których scenariuszów laboratoryjnych znajomości wymaga to zadanie? W szczególności czy FUSE powoduje, że jest ono prostsze niż w poprzednich latach? Pierwsze spojrzenie na FUSE pokazuje, że pozwala zapomnieć o modułach, ale może to tylko moje wrażenie…
  • (MK) FUSE nie wymaga żadnej babraniny z modułami, więc scenariusze z laba są tu chyba zbędne

Odpowiedzi na niektóre pytania:

Dzień dobry,
dziękuję za odpowiedź. Mam jeszcze kilka pytań odnośnie konkretnych
punktów:
"3. Stworzenie pliku lub katalogu przez użytkownika root skutkuje
usunięciem
prywatnych plików o tej samej nazwie. root nie może tworzyć plików
lub katalogów o takiej samej nazwie, jak istniejące pliki/katalogi,
których
jest właścicielem."
Tzn. jeśli root stworzy plik o nazwie takiej jak mój plik, a ja ten
plik mam otwarty, to przy odczycie będę widział to co root tam wpisał,
czy raczej będę pracował na swojej starej kopii, ale jak ją zamknę, to
już nie będzie do niej dostępu (tzn. efekt taki, jak gdyby plik został
skasowany, a potem utworzony przez roota)?

Zachodzi opcja numer 2 (efekt taki jak po skasowaniu pliku i jego
ponownym utworzeniu przez roota). Proces, ktory ten plik czytal, nadal
moze go czytac.

"6. Użytkownik, który usunie swoją kopię prywatną pliku, nie widzi go
(w szczególności nie może go otworzyć) do momentu jego ponownego
utworzenia
przez użytkownika root."
Czy oznacza to, że użytkownik nie może też utworzyć pliku o takiej
nazwie jak usunięta kopia prywatna? Jeśli tak, to jaki błąd powinien
być zgłaszany przy próbie utworzenia pliku lub katalogu o takiej samej
nazwie jak wcześniej usunięta kopia prywatna?

EEXIST. Nie będzie to może bardzo pomocny komunikat o błędzie, ale nic
ciekawszego się raczej nie znajdzie. Oczywiście, kiedy już root skasuje
plik 'bazowy' sytuacja wraca do normy.

"7. Usunięcie pliku powoduje jego natychmiastowe zniknięcie z systemu
plików,
tj. żaden nowy proces nie może go otworzyć. Jednakże procesy, w
których jest on
otwarty, powinny być w stanie dalej z tego pliku korzystać. W
szczególności,
różne procesy nadal widzą wzajemnie wprowadzane do pliku zmiany. Plik
należy
fizycznie usunąć, gdy ostatni proces dokona jego zamknięcia.
8. Usuwać i tworzyć katalogi może tylko root. Usunięcie katalogu powoduje
rekurencyjne usunięcie wszystkich jego podkatalogów, plików oraz
wszystkich ich
prywatnych kopii. Oznacza to, że zasada z punktu 7. nie przenosi się na
katalogi."
Jak należy rozumieć to, że pkt. 7. nie przenosi się na katalogi? Czy
jeśli root skasuje katalog z plikiem, który jest otwarty przez proces
zwykłego użytkownika, to dla tego procesu konsekwencje są takie, jakby
ten plik został po prostu skasowany?

Hmm, to jest trochę niejasno napisane. Chodziło mi zapewne o to, że gdy
root usuwa katalog, to kasują się wszystkie zawarte tam pliki (łącznie z
prywatnymi kopiami). To jest inna sytuacjia niż w przypadku, gdy root
kasuje plik, bo wtedy prywatne kopie nie są usuwane.
Z punktu widzenia procesu, który ma taki plik otwarty sytuacja jest taka
jak wtedy, gdy plik jest kasowany, tj. ten proces może nadal z pliku
korzystać.

Pozdrawiam,
Kuba Łącki

Uwagi prowadzących do rozwiązań:

Osoby poprawiające zadanie będą wdzięczne za uwagi prowadzących do rozwiązania z pierwszego terminu.

O ile nie zaznaczono inaczej, treść tej strony objęta jest licencją Creative Commons Attribution-ShareAlike 3.0 License