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.
FUSE składa się z dwóch głównych komponentów:
fuse.ko
)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:
libfuse-dev
(np. Debian), lub
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
):
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:
nodev
i nosuid
przy montowaniu przez użytkowników nieuprzywilejowanych.root
-a. Ma to zapobiec dwóm rodzajom zagrożeń: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
.
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źć 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:
struct fuse_operations
, w której ustawione zostaną wskaźniki odpowiadające zaimplementowanym funkcjom,fuse_main
:fuse_main(argc, argv, &my_ops);
main
,fuse_operations
. libfuse
:gcc hello.c `pkg-config fuse --cflags --libs` -o hello
Do tego celu należy mieć zainstalowany pakiet pkg-config
.
#define FUSE_USE_VERSION 26 #include <fuse.h> #include <stdio.h> #include <string.h> #include <errno.h> #include <fcntl.h> 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); }
gcc hello.c `pkg-config fuse --cflags --libs` -o hello
mkdir hellofs
./hello hellofs
root
:mount -t fuse hello hellofs
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
.hello
z katalogu hellofs
.hello
.fusermount -u hellofs
root
:umount hello
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:
fuse
znajdującego się w katalogu domowym użytkownika i wyświetlał ich nazwy jako pliki wirtualne.#define FUSE_USE_VERSION 26 /* ---------------------------------------------------------------------------- */ #include <fuse.h> #include <stdio.h> #include <string.h> #include <errno.h> #include <fcntl.h> #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<offset;++i) if(read(pfd[0], offbuf, 1) == 0) break; do { nread = read(pfd[0], outputbuf+totalread, size-totalread); totalread += nread; } while((nread != 0) && (totalread < size)); close(pfd[0]); } else { return -EACCES; } return totalread; } /* ---------------------------------------------------------------------------- */ static struct fuse_operations myfs_oper = { .getattr = myfs_getattr, .readdir = myfs_readdir, .open = myfs_open, .read = myfs_read, }; /* ---------------------------------------------------------------------------- */ int main(int argc, char *argv[]) { return fuse_main(argc, argv, &myfs_oper, NULL); } /* ---------------------------------------------------------------------------- */
#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 <stdlib.h> #include <stdio.h> #include <sys/types.h> #include <dirent.h> #include <string.h> #include <errno.h> #include <unistd.h> #include <pwd.h> /* --------------------------------------------------- */ /* 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
gcc myfs-main.c myfs_tools.c `pkg-config fuse --cflags --libs` -o fsname
zastąp fsname
swoją wymyśloną nazwą systemu plików.
fuse
a w nim plik id
:echo '#!/bin/bash' > ~/fuse/id echo 'id -u' >> ~/fuse/id
chmod 755 ~/fuse/id
id
.cat
.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.).echo '#!/bin/bash' > ~/fuse/procesy echo 'ps uax' >> ~/fuse/procesy
entry→d_type
.w
. Użyj pola st_mode
.access
, pread
.echo
spróbuj coś zapisać do tego pliku za pośrednictwem swojego systemu pliku:echo test > nazwa_pliku
fuse
np.:myfs/plik: Nie zaimplementowana funkcja
myfs_write
. Prototyp funkcji znajdziesz tutaj.pwrite
.stdout
na plikecho test > nazwa_pliku
echo test2 >> nazwa_pliku
myfs_write
czy użyta w niej funkcja open
używa flagi O_APPEND
.emacs
):id
.chmod
:chmod
.chmod a-x plik
chmod a+x plik
Poniżej przestawiona została mała (aczkolwiek interesująca) część różnych systemów plików napisanych w oparciu o FUSE:
Pełna lista systemów plików jest umieszczona na stronie wiki projektu oraz w Wikipedii.