Page 1 sur 2

Mon système de fichiers, le zFS

Publié : jeu. 1 oct. 2015 09:50
par Zedoune
Bonjour !

Je suis en train de développer un système de fichiers en utilisant Fuse, pour le moment c'est en perl pour le prototype. J'ai pas encore de nom, mais je pensais à zFS parce que j'aime bien les Z et qu'avec cette casse c'est pas encore pris :trololo:

Pour le moment j'ai implémenté les fonctions suivantes :

Code : Tout sélectionner

    getattr=>"main::e_getattr",
    getdir =>"main::e_getdir",
    open   =>"main::e_open",
    statfs =>"main::e_statfs",
    read   =>"main::e_read",
    write  =>"main::e_write",
    create =>"main::e_create",
    utime  =>"main::e_utime",
    unlink =>"main::e_unlink",
    mkdir  =>"main::e_mkdir",
je gère pas correctement les droits des fichiers, on peut pas les changer et ils appartiennent à root par défaut, et le montage envoie promener tout le monde s'il est pas root. Et je peux pas l'utiliser sans être root (même pas voir le contenu). On peut s'en servir

J'ai encore à gérer les liens physiques et symboliques (ça va etre compliqué pour les liens physiques je le sens :D )

j'hésite encore à implémenter la compression et la déduplication, et je sais pas si je fais mettre du copy-on-write pour gérer des snapshots, ça me semble bien trop compliqué pour l'usage que j'en souhaite.

Le principe c'est d'utiliser une base de données mongodb en réseau en "back-end", l'intérêt est d'avoir un système de fichiers distribué, facile à mettre en place et maintenir et qui coûte très peu.

Le code au 1er octobre 2015.

Code : Tout sélectionner

#!/usr/bin/env perl

use strict;
use warnings;

use Fuse;
use File::Basename;
use MongoDB;
use MongoDB::OID;
use Data::Dumper;
use Time::HiRes;
use POSIX qw(ENOENT EISDIR EINVAL);
use File::stat;
use File::Stat::Bits;
use threads;
use threads::shared;

use constant DEBUG => 1;
use constant DEBUGFUSE => 0;

my $client = MongoDB::MongoClient->new(
    host => '127.0.0.1',
    auto_reconnect => 1,
    auto_connect => 1,
    timeout => 10000
    );

my $db = $client->get_database('fs');
my $users = $db->get_collection('files');

my %TYPES = (
    "S_IFDIR" => S_IFDIR,
    "S_IFREG" => S_IFREG,
    "S_IFLNK" => S_IFLNK
    );

my ($debut,$fin);

sub e_utime {
    print "e_utime\n" if DEBUG;
    my $path = shift;
    my $atime = shift;
    my $ctime = shift;

    $users->update({ "path" => $path },	{'$set' =>{'atime' => "$atime"}});
    $users->update({ "path" => $path },	{'$set' =>{'ctime' => "$ctime"}});

    return 0;
}

sub e_unlink {
    my $path = shift;
    $users->remove( { "path" => $path });
    return 0;
}

sub e_rmdir {
    my $path = shift;
    my $check = $users->find({ "parent" => $path })->limit(1)->count();

    if($check == 0) { # le dossier est vide
	$users->remove( { "path" => $path });
	return 0;
    } else {
	return -1;
    }
}

sub e_create {
    my $path = shift;
    my $mask = shift;
    print "e_create : $path \n" if DEBUG;

    my $mode = 0b111111111;
    $mode &= $mask;

    # dossier parent et nom
    my ($name,$chemin) = fileparse $path;
    my $time = time();

    $users->insert(
	{
	    "path" => $path,
	    "parent" => $chemin,
	    "name" => $name,
	    "data" => undef,
	    "size" => 0,
	    "mode" => $mode,
	    "uid" => 0,
	    "gid" => 0,
	    "rdev" => 0,
	    "atime" => "$time",
	    "ctime" => "$time",
	    "mtime" => "$time",
	    "nlink" => 1,
	    "type" => "S_IFREG"
	});
    return 0;
}


sub e_symlink {
    my $dest = shift;
    my $path = shift;

    print "e_symlink -> $path $dest\n" if DEBUG;

    # dossier parent et nom
    my ($name,$chemin) = fileparse $path;
    my $time = time();
    
    $users->insert(
	{
	    "path" => $path,
	    "parent" => $chemin,
	    "dest" => $dest,
	    "name" => $name,
	    "size" => 0,
	    "mode" => 0777,
	    "uid" => 0,
	    "gid" => 0,
	    "rdev" => 0,
	    "atime" => "$time",
	    "ctime" => "$time",
	    "mtime" => "$time",
	    "nlink" => 1,
	    "type" => "S_IFLNK"
	});
    return 0;
}


sub e_mkdir {
    my $path = shift;
    my $mode = shift;

    print "e_mkdir -> $path\n" if DEBUG;

    # dossier parent et nom
    my ($name,$chemin) = fileparse $path;
    my $time = time();
    
    $users->insert(
	{
	    "path" => $path,
	    "parent" => $chemin,
	    "name" => $name,
	    "size" => 0,
	    "mode" => $mode,
	    "uid" => 0,
	    "gid" => 0,
	    "rdev" => 0,
	    "atime" => "$time",
	    "ctime" => "$time",
	    "mtime" => "$time",
	    "nlink" => 1,
	    "type" => "S_IFDIR"
	});
    return 0;
}

sub e_read {
    my ($path,$size,$offset,$fh) = @_;

    print "e_read -> $path \n" if DEBUG;

    my $file = $users->find_one({ "path" => $path});
    my $data = $file->{"data"};
    my $value;

    if($offset > 0 || $size != $file->{size})  {
	$value = substr $data, $offset, $size;
    } else {
	$value = $data;
    }

    return $value;
}

sub e_write {

    # chemin complet
    my $path = shift;

    print "e_write => $path\n" if DEBUG;

    # dossier parent et nom
    my ($name,$chemin) = fileparse $path;

    # contenu
    my $buffer = shift;

    # offset
    my $offset = shift;

    # result à enregistrer
    my $data;

    my $file = $users->find({ "path" => $path })->next;

    # si le fichier n'est pas vide
    if($file->{size} > 0) {
	
	# si on ajoute au fichier (offset = taille)
	if($offset >= $file->{size}) {
	    $data = $file->{data} . $buffer;
	} 
	
	# si on réécrit la fin et plus que la taille actuelle
	elsif ( ($offset+length($buffer)) >= $file->{size} && $offset > 0) {
	    print "$offset - " . length($buffer) . " TODO\n";
	}
	
	# si on écrase une partie du fichier
	else {
	    $data = substr($file->{data}, $offset, length($buffer), $buffer);
	}
    } else {
	$data = $buffer;
    }

    my $length = length($data);
    if($length > 16777216) {
	print "[ERROR] File to large (> 16 Mb) -> $path \n";
	return -1;
    } else {
	$users->update({ "path" => $path },	{'$set' =>{'data' => $data}});
	$users->update({ "path" => $path },	{'$set' =>{'size' => length($data)}});
    }

    return length($buffer);
}

sub e_getattr {
    my $node = shift;

    print "e_getattr : $node\n" if DEBUG;

    my $type;
    my $size = 0;
    my ($uid,$gid);
    my $mode;
    my ($atime, $ctime, $mtime);


    if($node eq "/") {
	$type = S_IFDIR;
	($uid,$gid) = (0,0);
	$mode = 0777;
	$mode |= $type;
	$atime = $ctime = $mtime = time()-1000;
    } else {
	my $name = $users->find( { path => $node } )->fields({ data => 0, _id => 0 })->next;

	return -ENOENT() unless exists($name->{name});	

	$size = $name->{size};
	$type = $TYPES{$name->{type}};
	$uid = $name->{uid};
	$gid = $name->{gid};
	$mode = $name->{mode};
	$mode |=  $type;
	$mtime = $name->{mtime};
	$ctime = $name->{ctime};
	$atime = $name->{atime};
    }

    my ($dev,$ino,$rdev, $blocks, $nlink, $blksize) = (0,0,0,1,1,1024);
    return ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size, $atime,$mtime,$ctime,$blksize,$blocks);
}

sub e_statfs {
    print "e_statfs\n";
    return 255, 2, 1, 1, 1, 1;
}

# formate le FS
sub fs_format {
    $db->drop();
}

sub e_open {
    print "e_open\n";
    return 0;
}

sub e_readdir {
    my $dir = shift;
    print "e_readdir => $dir \n" if DEBUG;

    $dir .= "/" if($dir ne "/");

    my @result;

    my $files = $users->find( { parent => $dir } )->fields({ data => 0, _id => 0 });
    my ($dev,$ino,$rdev, $blocks, $nlink, $blksize,$size) = (0,0,0,1,1,1024,0);
    
    while(my $name = $files->next) {
	my $type;
	my ($uid,$gid);
	my $mode;
	my ($atime, $ctime, $mtime);
    
	$size = $name->{size};
	$type = $TYPES{$name->{type}};
	$uid = $name->{uid};
	$gid = $name->{gid};
	$mode = $name->{mode};
	$mode |=  $type;
	$mtime = $name->{mtime};
	$ctime = $name->{ctime};
	$atime = $name->{atime};
	push(@result, [0,$name->{name}, [$dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size, $atime,$mtime,$ctime,$blksize,$blocks]]);
    }


    push(@result, 0);
    return @result;
}

# écrasé par e_readir
sub e_getdir {
    my $dir = shift;
    print "e_getdir => $dir \n" if DEBUG;

    $dir .= "/" if($dir ne "/");

    my $files = $users->find({ parent => $dir })->fields({name => 1});

    my @result;
    while(my $name = $files->next) {
	push(@result, $name->{name});
    }


    push(@result, 0);
    return @result;
}

sub e_truncate {
    print "e_truncate \n" if DEBUG;
    my $path = shift;
    my $offset = shift;

    my $file = $users->find_one({ "path" => $path});
    my $value;

    if($offset < $file->{"size"}) {
	my $data = $file->{"data"};
	$value = substr $data, 0, $offset;
	$users->update({ "path" => $path },	{'$set' =>{'data' => $data}});	
	$users->update({ "path" => $path },	{'$set' =>{'size' => length($data)}});	
    }

    return 0;
}

sub e_readlink {
    my $path = shift;
    print "e_readlink : $path\n" if DEBUG;

    my $file = $users->find({ "path" => $path })->fields({ dest => 1})->next;
    return $file->{dest};
}

my ($mountpoint) = "";
$mountpoint = shift(@ARGV) if @ARGV;
Fuse::main(
    mountpoint=>$mountpoint,
    mountopts => "big_writes",
    getattr   =>"main::e_getattr",
    getdir    =>"main::e_getdir",
    open      =>"main::e_open",
    statfs    =>"main::e_statfs",
    read      =>"main::e_read",
    write     =>"main::e_write",
    create    =>"main::e_create",
    utime     =>"main::e_utime",
    unlink    =>"main::e_unlink",
    mkdir     =>"main::e_mkdir",
    rmdir     =>"main::e_rmdir",
    truncate  =>"main::e_truncate",
    symlink   =>"main::e_symlink",
    readlink  =>"main::e_readlink",
    readdir   =>"main::e_readdir",
    threaded  => 1,
    debug     => DEBUGFUSE
    );



Mon système de fichiers, le zFS

Publié : jeu. 1 oct. 2015 09:59
par gizmo78
tu colle ca sur github? ca pourrait intéresser d'autres personnes et peut être t'aider

Mon système de fichiers, le zFS

Publié : jeu. 1 oct. 2015 10:01
par Zedoune
pour le moment c'est du bricolage, hier je savais même pas comment on faisait pour utiliser Fuse :lol:
on verra peut être quand ça commencera à marcher un peu mieux. Et Perl c'est censé être temporaire, faut voir l'overhead de perl par rapport à un autre langage pour ce truc là

Mon système de fichiers, le zFS

Publié : jeu. 1 oct. 2015 12:45
par Zedoune
J'ai augmenté drastiquement les performances d'écriture (la vitesse est raisonnable maintenant, c'est pas magique :o , avant ça déconnait énormément :D 800 Mo de transfert pour écrire un fichier de 2 Mo :sol: ), et en plus ça marche quand l'écriture se fait en plusieurs fois (un fichier dont la taille était supérieure à buffer récrasait à chaque fois le fichier avec le dernie buffer).

J'ai un gros problème à gérer mais c'est pas une surprise, j'étais aware :D Mongodb ne supporte pas des entrées de plus de 16 Mo, donc je peux pas stocker des fichiers de plus de 16 Mo. Donc soit j'utilise gridFS (un truc de mongodb pour stocker du fichier de grande taille), soit je découpe les fichiers en plusieurs morceaux, ce qui permettrait également de faire de la déduplication à un niveau de block et pas un niveau de fichiers.

Je pense aussi à terme compresser les données de manière très faible, ce qui devrait accélérer énormement le FS pour les petits fichiers textes sans charger le CPU.

Mon système de fichiers, le zFS

Publié : jeu. 1 oct. 2015 13:41
par kalistyan
[:sm++]

Mon système de fichiers, le zFS

Publié : jeu. 1 oct. 2015 17:53
par Zedoune
ça s'annonce beaucoup plus difficile que prévu d'avoir la compression

les fichiers sont écrits en morceaux d'une certaine taille, et on stocke les morceaux au fur et à mesure, le problème c'est que c'est difficile de savoir si on continue d'écrire à la suite du morceau ou si on doit écraser une partie du fichier.

Par exemple,

j'ai un fichier qui fait 100 octets, compressé il en fait 10. La lib fuse me dit d'écrire 10 octets à partir du 80 ème octet. Quand c'est compressé, je sais pas où ça tombe 80 octets.

Faudrait qu'à chaque lecture je décompresse le contenu pour le rajouter et pour recompresser.

A mon avis, il me manque du savoir faire là-dedans mais en l'état, je bloque là dessus ! :D

Mon système de fichiers, le zFS

Publié : jeu. 1 oct. 2015 18:24
par dsebire
Pour la compression, c'est comme ça que font tous les fs ;)

Mon système de fichiers, le zFS

Publié : jeu. 1 oct. 2015 18:43
par Zedoune
Moi j'ai un problème car je crée le fichier à coup de buffer, je travaille pas sur un fichier temporaire synchronisé ensuite...

Du coup à chaque fois que j'ajoute des données au fichier lors de sa construction je dois le rappatrier, concaténer et re enregistrer

Mon système de fichiers, le zFS

Publié : jeu. 1 oct. 2015 20:54
par Zedoune
Sous FreeBSD, ça plante, j'ai du corriger statfs (le truc donne des infos quand on fait df, mais linux ne s'en sert pas...). Et j'arrive pas à afficher le contenu d'un dossier ça crash avec une erreur, surement parce que je renvoie n'importe quoi pour plein d'attributs dont je vois pas l'utilité :D

C'est bien de travailler sur un système qui marche pas quand on met n'importe quoi !

Mon système de fichiers, le zFS

Publié : jeu. 1 oct. 2015 23:28
par lls
tu testes comment dans une vm ou sur un pc en dur ?

Mon système de fichiers, le zFS

Publié : jeu. 1 oct. 2015 23:41
par Zedoune
ben sur un pc en dur et un serveur à distance, situation réelle :D Le FS est déjà "presque" utilisable
pourquoi ?

Mon système de fichiers, le zFS

Publié : ven. 2 oct. 2015 00:01
par lls
Par curiosité justement pour voir si ce genre de dev pouvait se réaliser sur de la vm facilement

Mon système de fichiers, le zFS

Publié : ven. 2 oct. 2015 00:16
par Zedoune
Par curiosité justement pour voir si ce genre de dev pouvait se réaliser sur de la vm facilement
oui sans problème également ! Le système avec Fuse, tout se passe en mode utilisateur (bon faut être root pour moins se prendre la tête pour le démontage mais je suis barbare :D )

et sous linux / freebsd on peut altérer le réseau pour simuler une latence et/ou perte de paquet si on veut ajouter un peu de réalité dans les essais

Mon système de fichiers, le zFS

Publié : ven. 2 oct. 2015 12:10
par Zedoune
Mise à jour

[*]ça marche sous FreeBSD (l'implémentation de Fuse est plus stricte, donc j'ai du améliorer le code [:snooz]
[*]implémentation de readdir pour gagner énormément de performances quand on doit accéder à un dossier (il remplace getdir qui était utilisé à chaque fois avoir la liste des fichiers et pour chaque fichier du répertoire on faisait un appel getattr qui donne ses metadonnées, comme on est en réseau et que ça fait une requête à chaque fois, c'est LENT. readdir renvoie les infos du getattr pour chaque fichier d'un répertoire en UNE REQUETE bdd, du coup c'est très très rapide par rapport à getdir
[*]gestion des liens symboliques :sol: :sol: :sol:
[*]ajout de truncate (ça sert à créer des fichiers vides)

Mon système de fichiers, le zFS

Publié : ven. 2 oct. 2015 12:24
par Zedoune
je viens d'apprendre un truc TRES CON

sous linux, "ls" est souvent un alias de "ls --color=auto"

en mode ls --color le programme n'utilise pas readdir, du coup quand vous listez un répertoire un fait un appel système stat() par fichier au lieu d'avoir un appel groupé en une commande.

Quand c'est un dossier foireux avec des millions de fichiers, ça pose problème ! Du coup, pensez à utiliser /bin/ls quand vous tombez sur ce genre de dossiers (ça m'arrive une fois par un, un cache/temp applicatif qui dégénère :D )

Mon système de fichiers, le zFS

Publié : ven. 2 oct. 2015 13:48
par Zedoune
Maitenant je gère l'appel rename qui est très important car il sert à renommer ET à déplacer.
Du coup j'ai du revoir la façon dont je gérais la relation d'un noeud vis à vis de son parent, avant je stockais le chemin du parent. Si on changeait la parent d'endroit, il aurait fallu mettre à jour tout ce qui en découlait... pas cool :D

du coup maintenant ils ont l'identifiant du parent !

Mon système de fichiers, le zFS

Publié : sam. 3 oct. 2015 08:25
par augur1
Ipv6 ?

Mon système de fichiers, le zFS

Publié : sam. 3 oct. 2015 12:05
par Zedoune
Ipv6 ?
C'est le système qui gère ça donc oui pas de problème :)

Mon système de fichiers, le zFS

Publié : lun. 5 oct. 2015 12:22
par Zedoune
Maintenant le système de fichiers travaille avec des blocks, et plus une entrée "data" dans le document mongodb.
Les blocks sont compressés avec Snappy, algo de compression de google qui est très rapide et assez efficace (plus que gzip), l'utilité est de gagner du temps sur le réseau en transmettant des données compressées sans que ça ralentisse côté CPU.

Du fait que je travaille avec des blocks, il est possible d'avoir des blocks non rattachés à quelque chose, il faut donc que je fasse un outil de vérification pour voir s'il y a pas de blocks orphelins, et retravailler toute la partie d'écriture partielle / écrasement de fichiers. Avant on remplaçait une valeur, mainteantn faut supprimer les blocks et les réécrire, c'est un peu plus compliqué ^^

Mon système de fichiers, le zFS

Publié : lun. 5 oct. 2015 12:58
par poulpito
ca a pas l'air on dirait que tu fais un tetris
gros GG gros +1 d'admiration

Mon système de fichiers, le zFS

Publié : lun. 5 oct. 2015 13:02
par Zedoune
oh merci, c'est gentil ^^
Ce qui est marrant c'est que j'ai pas du tout l'impression que ce soit difficile, alors qu'en développement je suis vraiment nulle.

pour la compression suffit d'utiliser compress et decompress :)
j'ai du stocker les blocks dans un objet BSON de mongodb sinon avec l'utf8 et des caractères bizarres ça foutait la merde :D

magie de récupération décompressé

Code : Tout sélectionner

    my $file = $metadata->find_one(
	{ "path" => $path}, 
	{ blocks => { '$slice' => [$block_start, $block_count] }});


    foreach my $id (@{$file->{blocks}}) {
	print "i";
	my $data = $blocks->find_one({ _id => $id});
	$output .= decompress($data->{data});
    }

et à l'enregistrement

Code : Tout sélectionner

    if( $offset == 0 && exists($file->{blocks}) ) {
	print "e_write => suppression des blocks \n" if DEBUG;
	foreach my $id (@{$file->{blocks}}) {
	    $blocks->remove( { _id => $id });
	}
	$metadata->update( { path => $path },  {'$set' => { blocks => []}});
	$metadata->update( { path => $path },  {'$set' => { size => 0}});
    }

    for(my $i=0; $i< length($buffer) / BLKSIZE; $i++) {
	my $morceau = substr $buffer, $i * BLKSIZE, BLKSIZE;
	my $obj = MongoDB::BSON::Binary->new(data => compress($morceau));
	my $block_id = $blocks->insert( { data => $obj });
	$metadata->update( { path => $path },  {'$push' => { blocks => $block_id}});
	$metadata->update( { path => $path },  {'$inc' => { size => length($buffer)}});
    }

Mon système de fichiers, le zFS

Publié : lun. 5 oct. 2015 13:09
par poulpito
tu sais moi j'y connais rien alors j'admire juste ^^
et par contre ca se présente comment je veux dire pour utiliser
c'est au moment du montage ou tu définie ton FS que fuse va charger les modules de traitement propre à ton fs ?
explique a poulpi !

Mon système de fichiers, le zFS

Publié : lun. 5 oct. 2015 13:28
par Zedoune
Pour monter :D
/usr/bin/zFS.pl /media/reseau/

Pour démonter :D
fusermount -u /media/reseau/

Mon système de fichiers, le zFS

Publié : lun. 5 oct. 2015 13:40
par poulpito
ok et quand tu lance ton pl pour le montagne ca va charger tout le mécanisme des commandes et cie ou apres tu appel avec un mécanisme spécial ?

Mon système de fichiers, le zFS

Publié : lun. 5 oct. 2015 13:42
par Zedoune
en fait il se lance plus ou moins en démon, et Fuse s'occupe d'appeler la fonction qui va bien en fonction du besoin

si tu fais un ls dans le répertoire, fuse exécute readdir et renvoie ça à ls et tu obtiens la liste des fichiers/répertoires

si tu fais "cat fichier", t'as plein de read qui renvoie directement les données à la commande cat