====== FUSE - Wirtualne systemy plików w przestrzeni użytkownika ====== ===== Wstęp ===== * FUSE (ang. Filesystem in Userspace). * FUSE jest modułem jądra systemu, który umożliwia programowanie logiki systemu plików w przestrzeni użytkownika. * FUSE stanowi bazę popularnego w systemach GNU/Linux sterownika ntfs-3g, który umożliwia dostęp w trybie odczyt/zapis do systemu plików typu NTFS. W normalnych warunkach dodanie obsługi nowego typu systemu plików wymaga stworzenia i załadowania specjalnie do tego przeznaczonego modułu jądra. Ponadto, do montowania systemów plików zazwyczaj uprawniony jest tylko użytkownik ''root''. Jednak interfejs VFS (ang. //Virtual File System//) jądra coraz częściej z powodzeniem wykorzystuje się do zastosowań innych, niż zarządzanie danymi przechowywanymi na urządzeniach blokowych. Wobec tych zmian pojawia się potrzeba z jednej strony umożliwienia zwykłym użytkownikom montowania systemów plików i ładowania do nich sterowników na własny użytek i odpowiedzialność, z drugiej - ułatwienia pisania sterowników dla takich systemów nieopartych na urządzeniach blokowych. Framework Filesystem in Userspace (FUSE) jest jedną z możliwości realizacji tych potrzeb. ===== Zasada działania ===== FUSE składa się z dwóch głównych komponentów: * Modułu (''fuse.ko'') * Biblioteki dostępnej w przestrzeni użytkownika (''libfuse.so'') **W celu dodania możliwości rozwijania swoich własnych systemów plików należy wykonać jedną z poniższych czynności:** * Zainstalować pakiet ''libfuse-dev'' (np. Debian), lub * ściągnąć kod źródłowy biblioteki ze strony [[http://fuse.sourceforge.net]], skompilować i zainstalować. Systemy plików oparte na FUSE obsługiwane są przez proces działający w przestrzeni użytkownika (tzw. filesystem daemon). Każdy system plików jest obsługiwany przez osobny daemon. Właścicielem procesu-daemona jest użytkownik, który zamontował obsługiwany przez niego system plików (a więc nie musi to być ''root'') :!: Warto zwrócić uwagę, że w jednej chwili może być uruchomionych wiele daemonów obsługujących systemy plików tego samego typu, nawet z tym samym właścicielem. Operacje na systemie plików obsługiwane przez daemona są dostępne przez interfejs VFS jadra dzięki modułowi ''fuse.ko''. Komunikacja pomiędzy modułem ''fuse.ko'' a kodem obsługującym system plików jest realizowana przez bibliotekę ''libfuse''. Przepływ żądania przy przykładowej operacji na systemie plików opartym na FUSE dobrze ilustruje poniższy obrazek (w tym przykładzie plik ''example/hello'' jest daemonem obsługującym system plików zamontowany w katalogu ''/tmp/fuse''): {{ :pl:dydaktyka:sitw:fuse_structure.png?direct |}} ===== Bezpieczeństwo ===== Umożliwienie nieuprzywilejowanym użytkownikom tworzenia i montowania własnych systemów plików może pociągać za sobą wiele zagrożeń: dla całego systemu lub innych użytkowników. Najbardziej oczywistym zapobiega się przez: * Ustawianie opcji ''nodev'' i ''nosuid'' przy montowaniu przez użytkowników nieuprzywilejowanych. * Sprawdzanie uprawnień użytkownika montującego do modyfikowania punktu montowania. * Dodatkowo uniemożliwia się dostęp do zamontowanego przez nieuprzywilejowanego użytkownika systemu plików wszystkim pozostałym użytkownikom, włączając w to ''root''-a. Ma to zapobiec dwóm rodzajom zagrożeń: * Dostarczony system plików nie musi spełniać żadnych warunków. Np. wywołana na nim operacja może się nigdy nie zakończyć. Większość programów (w tym również te kluczowe dla stabilności szeroko pojmowanego systemu) nie jest na to przygotowana. * Filesystem daemon może gromadzić informacje o wykonywanych operacjach i przekazywać je swojemu właścicielowi. Jest to potencjalny wyciek informacji. Powyższe ograniczenie możemy znieść, montując system plików z opcją ''allow_root'' lub ''allow_other'' (aby umożliwić dostęp odpowiednio ''root''-owi lub wszystkim użytkownikom). Jeśli jednak system jest montowany przez użytkownika nieuprzywilejowanego, to aby mógł użyć tych opcji, w pliku konfiguracyjnym FUSE (''/etc/fuse.conf''), musi być obecna opcja ''user_allow_other''. ===== Implementacja w C ===== FUSE definuje listę operacji możliwych do wykonania w ramach implementowanego systemu plików. Lista tych operacji jest przedstawiona w automatycznie wygenerowanej dokumentacji do biblioteki, którą można znaleźć [[http://fuse.sourceforge.net/doxygen/structfuse__operations.html|tutaj]]. Wszystkie z powyższych operacji są zwykłymi wskaźnikami do funkcji, które zostały zgromadzone w strukturze ''fuse_operations''. W większości ich deklaracja oraz semantyka jest podobna lub identyczna do standardowych funkcji operujących na plikach. Wyjątkiem jest przekazywanie informacji o błędach: **Zamiast ustawiać odpowiednią wartość zmiennej ''errno'', funkcje powinny zwrócić wartość ujemną, równą co do wartości bezwzględnej kodowi błędu**. Implementacja systemu plików sprowadza się do: - zaimplementowania wybranych funkcji, - utworzenia zmiennej typu ''struct fuse_operations'', w której ustawione zostaną wskaźniki odpowiadające zaimplementowanym funkcjom, - wywołania funkcji ''fuse_main'':fuse_main(argc, argv, &my_ops); * pierwsze dwa argumenty są analogiczne do wywołania funkcji ''main'', * ostatni wskazuje na wartość struktury ''fuse_operations''. - skompilowanie programu i zlinkowanie go go z biblioteką ''libfuse'':gcc hello.c `pkg-config fuse --cflags --libs` -o helloDo tego celu należy mieć zainstalowany pakiet ''pkg-config''. ===== Instrukcje do wykonania ===== ==== - HelloWorld ==== - Prześledzić, pobrać poniższy program: #define FUSE_USE_VERSION 26 #include #include #include #include #include static const char *hello_str = "Hello World!\n"; static const char *hello_path = "/hello"; static int hello_getattr(const char *path, struct stat *stbuf) { int res = 0; memset(stbuf, 0, sizeof(struct stat)); if (strcmp(path, "/") == 0) { stbuf->st_mode = S_IFDIR | 0755; stbuf->st_nlink = 2; } else if (strcmp(path, hello_path) == 0) { stbuf->st_mode = S_IFREG | 0444; stbuf->st_nlink = 1; stbuf->st_size = strlen(hello_str); } else res = -ENOENT; return res; } static int hello_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi) { (void) offset; (void) fi; if (strcmp(path, "/") != 0) return -ENOENT; filler(buf, ".", NULL, 0); filler(buf, "..", NULL, 0); filler(buf, hello_path + 1, NULL, 0); return 0; } static int hello_open(const char *path, struct fuse_file_info *fi) { if (strcmp(path, hello_path) != 0) return -ENOENT; if ((fi->flags & 3) != O_RDONLY) return -EACCES; return 0; } static int hello_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi) { size_t len; (void) fi; if(strcmp(path, hello_path) != 0) return -ENOENT; len = strlen(hello_str); if (offset < len) { if (offset + size > len) size = len - offset; memcpy(buf, hello_str + offset, size); } else size = 0; return size; } static struct fuse_operations hello_oper = { .getattr = hello_getattr, .readdir = hello_readdir, .open = hello_open, .read = hello_read, }; int main(int argc, char *argv[]) { return fuse_main(argc, argv, &hello_oper, NULL); } - Skompilować program poleceniem:gcc hello.c `pkg-config fuse --cflags --libs` -o hello - Zamontować system plików na dwa sposoby: - Jak zwykły użytkownik: * ''mkdir hellofs'' * ''./hello hellofs'' - Jako ''root'': * ''mount -t fuse hello hellofs'' - W każdym przypadku spróbować wejść (''cd'') do katalogu ''hellofs'' i wyświetlić listę  plików (''ls''). Można też spróbować użyć do tego celu managera plików ''nautilus''. - Proszę spróbować przeczytać plik ''hello'' z katalogu ''hellofs''. - Odnaleźć w kodzie programu miejsca gdzie zdefiniowane zostały: * Nazwa pliku ''hello''. * Prawa dostępu do katalogu i pliku. * Odczyt zawartości katalogu. * Odczyt zawartości pliku. - Odmontować system plików: - Jako zwykły użytkownik: * ''fusermount -u hellofs'' - Jako ''root'': * ''umount hello'' ==== - Własny system plików ==== Celem tego ćwiczenia będzie stworzenie własnego, prostego systemu plików, którego zadaniem będzie pobieranie informacji o systemie. Działanie systemu plików będzie następujące: * Nasz sterownik systemu plików będzie pobierał listę plików z katalogu ''fuse'' znajdującego się w katalogu domowym użytkownika i wyświetlał ich nazwy jako pliki wirtualne. * Każdy taki plik wykonywalny będzie wykonywał prostą operację mającą na celu odczytanie informacji o systemie np. * Nazwa systemu. * Lista zalogowanych osób. * Informacje o pamięci. * Informacje o procesorze. * itp. * Odczyt któregokolwiek z wirtualnych plików wykonywalnych będzie powodował uruchomienie pliku wykonywalnego i przechwycenie jego wyjścia, które zostanie zaprezentowane jako zawartość odczytywanego pliku wirtualnego. * Efektem odczytu któregokolwiek z wirtualnych plików niewykonywalnych będzie zwykłe przeczytanie jego treści. * Dodatkowo nasz system plików będzie pozwalał na: * Zmianę praw dostępu do pliku. * Zapis pliku jeżeli jest to plik niewykonywalny. === Ćwiczenia === == Ćwiczenie 1 - Analiza programu == - Pobierz poniższy program oraz komplementarne do niego pliki: #define FUSE_USE_VERSION 26 /* ---------------------------------------------------------------------------- */ #include #include #include #include #include #include "myfs_tools.h" /* ---------------------------------------------------------------------------- */ static const char *hello_str = "Hello World!\n"; /* ---------------------------------------------------------------------------- */ static int myfs_getattr(const char *path, struct stat *stbuf) { int res = 0; memset(stbuf, 0, sizeof(struct stat)); if (strcmp(path, "/") == 0) { stbuf->st_mode = S_IFDIR | 0755; stbuf->st_nlink = 2; } else if(myfs_file_exists(path)) { char* fullpath = myfs_file_full_path(path); stat(fullpath, stbuf); free(fullpath); } else res = -ENOENT; return res; } /* ---------------------------------------------------------------------------- */ static int myfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi) { (void) offset; (void) fi; if (strcmp(path, "/") != 0) return -ENOENT; filler(buf, ".", NULL, 0); filler(buf, "..", NULL, 0); // open dir char* dirname = myfs_dir_get_fuse_home_path(); DIR* dirptr = myfs_dir_open(dirname); free(dirname); if(dirptr == NULL) return -errno; // read dir content while(1) { struct dirent * entry = myfs_dir_read(dirptr); if(!entry) break; filler(buf, entry->d_name, NULL, 0); } // close dir myfs_dir_close(dirptr); return 0; } /* ---------------------------------------------------------------------------- */ static int myfs_open(const char *path, struct fuse_file_info *fi) { if(myfs_file_exists(path) == 0) return -ENOENT; if ((fi->flags & 3) != O_RDONLY) return -EACCES; return 0; } /* ---------------------------------------------------------------------------- */ static int myfs_read(const char *path, char *outputbuf, size_t size, off_t offset, struct fuse_file_info *fi) { pid_t pid; int pfd[2], i; char offbuf[1]; ssize_t nread; ssize_t totalread = 0; (void) fi; pipe(pfd); pid = fork(); // child process if(pid == 0) { dup2(pfd[1],STDOUT_FILENO); close(pfd[0]); close(pfd[1]); char* fullpath = myfs_file_full_path(path); execlp(fullpath, fullpath, NULL); // on error close(STDOUT_FILENO); exit(0); } // parent process else if(pid > 0) { close(pfd[1]); for(i=0;i #include "myfs_tools.h" /* ---------------------------------------------------------------------------- */ char* myfs_dir_get_fuse_home_path() { struct passwd *pw = getpwuid(getuid()); char* result; if(pw == NULL) return NULL; result = (char*)malloc(sizeof(char)*(strlen(pw->pw_dir))+1+5); strcpy(result, pw->pw_dir); strcat(result, "/fuse"); return result; } /* ---------------------------------------------------------------------------- */ char* myfs_file_full_path(const char* fusepath) { char* dirname = myfs_dir_get_fuse_home_path(); char* fullpath = (char*)malloc(sizeof(char)*(strlen(dirname) + strlen(fusepath) + 1)); strcpy(fullpath, dirname); strcat(fullpath, fusepath); free(dirname); return fullpath; } /* ---------------------------------------------------------------------------- */ int myfs_file_exists(const char* fusepath) { char* fullpath = myfs_file_full_path(fusepath); int result = (access(fullpath, F_OK) != -1) ? 1 : 0; free(fullpath); return result; } /* ---------------------------------------------------------------------------- */ DIR* myfs_dir_open(char* dir_name) { return opendir(dir_name); } /* ---------------------------------------------------------------------------- */ struct dirent* myfs_dir_read(DIR* d) { return readdir(d); } /* ---------------------------------------------------------------------------- */ int myfs_dir_close(DIR* d) { return closedir(d) == 0; } /* ---------------------------------------------------------------------------- */ #ifndef MYFS_TOOLS_H #define MYFS_TOOLS_H /* --------------------------------------------------- */ #include #include #include #include #include #include #include #include /* --------------------------------------------------- */ /* Tworzy sciezke do plikow wykonywalnych dla obecnego uzytkownika */ char* myfs_dir_get_fuse_home_path(); /* Tworzy pelna sciezke do pliku wylonywalnego na podstawie sciezki fuse */ char* myfs_file_full_path(const char* fusepath); /* Sprawdza czy plik okreslony przez sciezke fuse rzeczywiscie istnieje w normalnym systemie plikow */ int myfs_file_exists(const char* fusepath); /* Owtiera katalog do czytania */ DIR* myfs_dir_open(char* dir_name); /* Pobiera kolejna pozycje z katalogu do czytania */ struct dirent* myfs_dir_read(DIR* d); /* Zamyka otwarty katalog */ int myfs_dir_close(DIR* d); /* --------------------------------------------------- */ #endif - Skompiluj program poleceniemgcc myfs-main.c myfs_tools.c `pkg-config fuse --cflags --libs` -o fsnamezastąp ''fsname'' swoją wymyśloną nazwą systemu plików. - W swoim katalogu domowym stwórz folder ''fuse'' a w nim plik ''id'': echo '#!/bin/bash' > ~/fuse/id echo 'id -u' >> ~/fuse/id - Ustaw odpowiednie prawa dostępu:chmod 755 ~/fuse/id - W miejscu gdzie tworzony jest plik wykonywalny programu stwórz folder, który będzie punktem montowania nowego systemu plików. - Zamontuj swój system plików. - Sprawdź zawartość katalogu - powinien pojawić się plik ''id''. - Spróbuj wyświetlić plik używając np. programu ''cat''. - Przeanalizuj program w celu zrozumienia otrzymanego rezultatu: - Przy pomocy ''ls -l'' wyświetl zawartość katalogu, skąd się wzięły takie a nie inne atrybuty pliku (prawa dostępu, typ pliku, rozmiar, właściciel, itp.). - Dlaczego wyświetlenie pliku powoduje uruchomienie skryptu? Gdzie to jest zdefiniowane w programie? == Ćwiczenie 2 - Wyświetlanie zawartości pliku == - W swoim katalogu domowym stwórz drugi skrypt: echo '#!/bin/bash' > ~/fuse/procesy echo 'ps uax' >> ~/fuse/procesy - Ustaw prawa do wykonywania. - Zamontuj swój system plików i przy jego pomocy spróbuj wyświetlić zawartość powyższych plików i odpowiedzieć na pytania: * Czy pliki wyświetla się poprawnie? * Dlaczego otrzymano taki efekt? - :!: Spróbuj naprawić powstały błąd. == Ćwiczenie 3 - Atrybuty plików == - Rozbuduj program tak aby wyświetlał tylko pliki regularne: Użyj pola ''entry->d_type''. - Rozbuduj program tak aby pliki wykonywalne miały usunięty atrybut zapisu ''w''. Użyj pola ''st_mode''. - Rozbuduj program tak aby efektem odczytu plików które możemy uruchomić był odczyt rezultatów ich działania natomiast plików niewykonywalnych (z naszego punktu widzenia) wyświetlenie ich treści. Użyj funkcji ''access'', ''pread''. == Ćwiczenie 4 - Zapis i odczyt do/z plików == - W swoim katalogu domowych stwórz plik o dowolnej nazwie, który posiada prawo do zapisu i jednocześnie nie posiada prawa do uruchamiania. - Przy pomocy ''echo'' spróbuj coś zapisać do tego pliku za pośrednictwem swojego systemu pliku:echo test > nazwa_pliku - Dlaczego wypisywany jest komunikat o braku dostępu (przecież masz prawo do zapisu) :?: :!: - Jak pozbyć się tego komunikatu i sprawić, żeby w przypadku próby zapisu system informował o niezaimplementowanej funkcji ''fuse'' np.:myfs/plik: Nie zaimplementowana funkcja - Rozbuduj program o możliwość zapisu do pliku: - Dopisz implementację funkcji ''myfs_write''. Prototyp funkcji znajdziesz [[http://fuse.sourceforge.net/doxygen/structfuse__operations.html#a897d1ece4b8b04c92d97b97b2dbf9768|tutaj]]. - Dopisz nazwę funkcji w strukturze definiującej listę zaimplementowanych operacji. - Napisz implementację funkcji. Użyj ''pwrite''. - Spróbuj ponownie zapisać do pliku jakieś dane: - Czy teraz jest to możliwe? - Na czym polega problem? - Zaimplementuj brakującą funkcję. - Ponownie wykonaj zapis: - Użyj zwykłego przekierowania ''stdout'' na plikecho test > nazwa_pliku - Użyj dopisywania do plikuecho test2 >> nazwa_pliku - Sprawdź czy otrzymane rezultaty są poprawne. - Jeżeli nie poszukaj błędu: sprawdź funkcję ''myfs_write'' czy użyta w niej funkcja ''open'' używa flagi ''O_APPEND''. - Jeżeli wszystko działa sprawdź czy (możesz użyć jakiegoś edytora tekstowego np. ''emacs''): - możesz czytać pliki wykonywalne -> powinno być to możliwe, a rezultatem powinno być wyjście z procesu. - możesz czytać pliki niewykonywalne z odjętym prawem do czytania -> nie powinno być to możliwe, próba odczytu powinna skutkować komunikatem o braku dostępu. - możesz czytać pliki niewykonywalne prawem do czytania -> powinno być to możliwe. - możesz zapisywać do plików wykonywalnych -> nie powinno być to możliwe, próba zapisu powinna skutkować komunikatem o braku dostępu. - możesz zapisywać do plików niewykonywalnych, z ustawionym prawem do zapisu -> powinno być to możliwe. - możesz zapisywać do plików niewykonywalnych, z odjętym prawem do zapisu -> nie powinno być to możliwe, próba zapisu powinna skutkować komunikatem o braku dostępu. - Jeżeli w którymkolwiek punkcie zachowanie twojego systemu plików jest inne popraw jego działanie. == Ćwiczenie 5 - Prawa dostępu == - Spróbuj zmienić prawa dostępu do jednego z plików np. ''id''. - Znowu pojawia się komunikat o niezaimplementowanej funkcji. Dodaj brakującą funkcję ''chmod'': - Jej prototyp znajdziesz [[http://fuse.sourceforge.net/doxygen/structfuse__operations.html#a897d1ece4b8b04c92d97b97b2dbf9768|tutaj]]. - Do implementacji funkcji użyj funkcji systemowej ''chmod''. - Dodaj zaimplementowaną funkcję do listy dozwolonych operacji w systemie plików. - Sprawdź działanie praw dostępu: - Wyświetl plik wykonywalny. - Zmień prawa dostępuchmod a-x plik - Wyświetl plik ponownie. - Jaki jest rezultat? - Zmień wybrany plik zapisując do niego inne polecenie do wykonania: - Zmodyfikuj wybrany plik przy pomocy edytora (np. emacs, pico, vi) lub przekierowania strumienia. - Wyświetl plik wykonywalny -> powinieneś otrzymać treść skryptu. - Zmień prawa dostępuchmod a+x plik - Wyświetl plik ponownie -> powinieneś otrzymać rezultat działania skryptu. - Gratulacje! Teraz przy pomocy swojego systemu plików możesz edytować skrypty i je uruchamiać :!: :-) - Stwórz odpowiednie pliki, które pobierają wybrane informacje o sprzęcie, systemie operacyjnym, etc. ===== Ciekawe systemy plików ===== Poniżej przestawiona została mała (aczkolwiek interesująca) część różnych systemów plików napisanych w oparciu o FUSE: * SSHFS - pozwala zamontować zdalne drzewo katalogów za pośrednictwem ssh * GmailFS - wykorzystuje konto Gmail do składowania plików dowolnej wielkości. Napisany w Pythonie. Jego używanie jest niezgodne z zasadami użytkowania konta Gmail. Po zmianach w interfejsie usługi, przestał działać i być rozwijany. Istnieje jednak fork, który powinien działać. * TrueCrypt - popularne narzędzie do szyfrowania danych "w locie". * BloggerFS - umożliwia zarządzanie wpisami na platforme Blogger. * FilterFS, rofs-filtered - umożliwiają montowanie lokalnego drzewa katalogów w innym miejscu, z filtrowaniem udostępnianych plików. * TSKmount-fuse - system oparty na urządzeniu blokowym. Pozwala na odzyskiwanie skasowanych plików z systemu plików istniejącego na urządzeniu (o ile typ systemu plików na to pozwala). * TagsFS - system plików organizowany przez tagi zamiast klasycznego drzewa katalogów. Pełna lista systemów plików jest umieszczona na stronie [[http://sourceforge.net/p/fuse/wiki/FileSystems|wiki projektu]] oraz w [[http://en.wikipedia.org/wiki/Filesystem_in_Userspace|Wikipedii]]. ===== Źródła ===== * [[http://fuse.sourceforge.net|Strona projektu]] * Komunikaty pomocy do fusermount i sshfs * [[http://www.ibm.com/developerworks/linux/library/l-fuse|Develop your own filesystem with FUSE]] * [[http://fuse.sourceforge.net/doxygen/index.html|Automatycznie wygenerowana dokumentacja libfuse]] * [[http://students.mimuw.edu.pl/ZSO/Wyklady/00_stud/fuse/fuse.html|FUSE (Filesystem in Userspace) - Wojciech Baranowski]]