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 floor ceil);
use File::stat;
use File::Stat::Bits;
use threads;
use threads::shared;
use Compress::Snappy;
use constant DEBUG => 1;
use constant DEBUGFUSE => 0;
use constant BLKSIZE => 32768;
my $host = shift;
my $client = MongoDB::MongoClient->new(
host => $host,
auto_reconnect => 1,
auto_connect => 1,
timeout => 10000,
);
my $db = $client->get_database('fs');
my $fs = $db->get_collection('files');
my %TYPES = (
"S_IFDIR" => S_IFDIR,
"S_IFREG" => S_IFREG,
"S_IFLNK" => S_IFLNK
);
my ($debut,$fin);
sub get_dir_id {
my $path = shift;
$path =~ s|/$||;
my $id = 0;
print "get_dir_id : $path\n" if DEBUG;
if (length($path) > 1) {
my $node = $fs->find({ "path" => $path })->limit(1)->fields({ _id => 1})->next;
$id = $node->{_id};
}
return $id;
}
sub e_chmod {
my $path = shift;
my $mode = shift;
print "e_chmod : $path $mode\n" if DEBUG;
$fs->update({ "path" => $path }, {'$set', =>{'mode' => $mode}});
return 0;
}
sub e_chown {
my $path = shift;
my $uid = shift;
my $gid = shift;
print "e_chown : $path $uid $gid\n" if DEBUG;
$fs->update({ "path" => $path }, {'$set', =>{'uid' => $uid}});
$fs->update({ "path" => $path }, {'$set', =>{'gid' => $gid}});
return 0;
}
sub e_utime {
print "e_utime\n" if DEBUG;
my $path = shift;
my $atime = shift;
my $ctime = shift;
$fs->update({ "path" => $path }, {'$set' =>{'atime' => "$atime"}});
$fs->update({ "path" => $path }, {'$set' =>{'ctime' => "$ctime"}});
return 0;
}
sub e_unlink {
my $path = shift;
print "e_unlink : $path\n" if DEBUG;
my $file = $fs->find_one({ "path" => $path});
$fs->remove({ file_parent => $file->{_id}});
$fs->remove( { "path" => $path });
return 0;
}
sub e_rename {
my ($old, $new) = @_;
print "e_rename : $old $new \n" if DEBUG;
my ($name,$chemin) = fileparse $new;
my $parent = get_dir_id $chemin;
my $file = $fs->find_one({ "path" => $new});
if(exists($file->{path})) {
e_unlink $new;
}
$fs->update({ "path" => $old }, {'$set' =>{'name' => $name}});
$fs->update({ "path" => $old }, {'$set' =>{'parent' => $parent}});
$fs->update({ "path" => $old }, {'$set' =>{'path' => $new}});
return 0;
}
sub e_rmdir {
my $path = shift;
my $parent = get_dir_id $path;
my $check = $fs->find({ "parent" => $parent })->limit(1)->count();
if($check == 0) { # le dossier est vide
$fs->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();
my $parent = get_dir_id $chemin;
$fs->insert(
{
path => $path,
parent => $parent,
name => $name,
data => undef,
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();
my $parent = get_dir_id $chemin;
$fs->insert(
{
path => $path,
parent => $parent,
dest => $dest,
name => $name,
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();
my $parent = get_dir_id $chemin;
$fs->insert(
{
path => $path,
parent => $parent,
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) = @_;
my $block_start = floor($offset / BLKSIZE);
my $block_count = ceil($size / BLKSIZE);
my $output = undef;
print "e_read -> $path : $size : $offset : $block_start : $block_count \n" if DEBUG;
my $file = $fs->find_one(
{ "path" => $path},
{ blocks => { '$slice' => [$block_start, $block_count] }});
if(exists($file->{blocks})) {
my $blocks = $fs->find( { _id => { '$in' => $file->{blocks} }})->sort([block_number => 1]);
while(my $data = $blocks->next) {
$output .= decompress($data->{data});
}
$output = substr $output, $offset % BLKSIZE, $size;
}
return $output;
}
sub e_write {
# chemin complet
my $path = shift;
# contenu
my $buffer = shift;
# offset
my $offset = shift;
# taille buffer
my $size = length($buffer);
# result à enregistrer
my $data;
print "e_write => $path : $size $offset \n" if DEBUG;
my $file = $fs->find_one({ "path" => $path });
if( $offset == 0 && exists($file->{blocks}) ) {
print "e_write => suppression des blocks \n" if DEBUG;
foreach my $id (@{$file->{blocks}}) {
$fs->remove( { _id => $id });
}
$fs->update( { path => $path }, {'$set' => { blocks => []}});
$fs->update( { path => $path }, {'$set' => { size => 0}});
}
if($size >= BLKSIZE) {
for(my $i=0; $i< $size / BLKSIZE; $i++) {
my $block = $i + $offset / BLKSIZE;
print "block : $block\n";
my $morceau = substr $buffer, $i * BLKSIZE, BLKSIZE;
my $obj = MongoDB::BSON::Binary->new(data => compress($morceau));
my $block_id = $fs->insert( { data => $obj, file_parent => $file->{_id}, block_number => $block });
$fs->update( { path => $path }, {'$push' => { blocks => $block_id}});
$fs->update( { path => $path }, {'$inc' => { size => length($morceau)}});
}
}
# TODO : gérer une fin de bloc qui va sur un nouveau bloc
if($size find_one( { file_parent => $file->{_id}, block_number => $block });
my $obj = MongoDB::BSON::Binary->new(data => compress(decompress($partial->{data}).$buffer));
$fs->update( { _id => $partial->{_id} }, {'$set' => { data => $obj}});
}
else { # nouveau bloc
print "nouveau block $block\n";
my $obj = MongoDB::BSON::Binary->new(data => compress($buffer));
my $block_id = $fs->insert( { data => $obj, file_parent => $file->{_id}, block_number => $block });
$fs->update( { path => $path }, {'$push' => { blocks => $block_id}});
}
}
$fs->update( { path => $path }, {'$inc' => { size => $size}});
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 = $fs->find( { path => $node } )->next;
return -ENOENT() unless exists($name->{name});
if (exists($name->{size})) {
$size = $name->{size};
} else {
$size = 0;
}
$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,0,1,BLKSIZE);
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, BLKSIZE, BLKSIZE, 1;
}
sub e_open {
print "e_open\n";
return 0;
}
sub e_readdir {
my $path = shift;
print "e_readdir => $path \n" if DEBUG;
my $parent = get_dir_id $path;
my @result;
my $files = $fs->find( { parent => $parent } )->fields({ _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;
}
sub e_truncate {
print "e_truncate \n" if DEBUG;
my $path = shift;
my $offset = shift;
my $file = $fs->find_one({ "path" => $path});
my $value;
if($offset < $file->{"size"}) {
my $data = $file->{"data"};
$value = substr $data, 0, $offset;
$fs->update({ "path" => $path }, {'$set' =>{'data' => $data}});
$fs->update({ "path" => $path }, {'$set' =>{'size' => length($data)}});
}
return 0;
}
sub e_readlink {
my $path = shift;
print "e_readlink : $path\n" if DEBUG;
my $file = $fs->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",
rename =>"main::e_rename",
chown =>"main::e_chown",
chmod =>"main::e_chmod",
threaded => 0,
debug => DEBUGFUSE
);
reste à gérer une écriture qui empiète à moitié sur un bloc et qui en écrit sur un autre, pour le moment aç fait de la merde mais avec des programmes "normaux" c'est pas censé arriver (souvent on utilise des puissances de 2 en taille de buffer et d'offset.
et j'ai plein de vérifications à rajouter par-ci par-là pour avoir des erreurs qui correspondent bien à ce qui arrive