Mon système de fichiers, le zFS
Publié : jeu. 1 oct. 2015 09:50
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 :
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
)
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.
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",
J'ai encore à gérer les liens physiques et symboliques (ça va etre compliqué pour les liens physiques je le sens

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
);