# Declare our package package POE::Component::Fuse::AsyncFsV; use strict; use warnings; # Initialize our version use vars qw( $VERSION ); $VERSION = '0.05'; # load the aio helper use POE::Component::AIO { no_auto_bootstrap => 1, no_auto_export => 1 }; # we need access to the kernel use POE; # constants we need to interact with FUSE use Errno qw( :POSIX ); # ENOENT EISDIR etc use Fcntl qw( :DEFAULT :mode ); # S_IFREG S_IFDIR, O_SYNC O_LARGEFILE etc # get the refaddr of our FHs use Scalar::Util qw( openhandle ); # creates a new instance sub new { my $class = shift; my $fsv = shift; # init PoCo-AIO POE::Component::AIO->new(); # Create our obj and return it return bless { 'fsv' => $fsv, 'fhmap' => {}, }, $class; } # shutdown PoCo-AIO sub DESTROY { POE::Component::AIO->new()->shutdown(); return; } # simple accessor sub fsv { return shift->{'fsv'}; } # simple callback that returns 0 for FUSE sub _cb { my( $self, $name ) = @_; # make our custom wrapper my $cb = $poe_kernel->get_active_session->postback( 'reply', $name ); my $callback = sub { if ( defined $_[0] ) { # IO::AIO returns 0 as successful... if ( $_[0] == 0 ) { # FUSE needs 0 as retval $cb->( 0 ); } else { # error code $cb->( $_[0] ); } } else { $cb->( -EINVAL() ); } }; return $callback; } sub getattr { my ( $self, $context, $path ) = @_; # make a special wrapper my $cb = $poe_kernel->get_active_session->postback( 'reply', 'getattr' ); my $callback = sub { if ( defined $_[0] ) { if ( ref $_[0] ) { $cb->( @{ $_[0] } ); } else { # error code $cb->( $_[0] ); } } else { # not found $cb->( -ENOENT() ); } }; $self->fsv->stat( $path, $callback ); return; } sub getdir { my ( $self, $context, $path ) = @_; # make a special wrapper my $cb = $poe_kernel->get_active_session->postback( 'reply', 'getdir' ); my $callback = sub { if ( defined $_[0] ) { if ( ref $_[0] ) { # FUSE needs 0 at the end of list to signify success $cb->( @{ $_[0] }, 0 ); } else { # error code or empty list = 0 $cb->( $_[0] ); } } else { $cb->( -EINVAL() ); } }; $self->fsv->readdir( $path, $callback ); return; } sub getxattr { my ( $self, $context, $path, $attr ) = @_; # we don't have any extended attribute support $poe_kernel->yield( 'reply', [ 'getxattr' ], [ 0 ] ); return; } sub setxattr { my ( $self, $context, $path, $attr, $value, $flags ) = @_; # we don't have any extended attribute support $poe_kernel->yield( 'reply', [ 'setxattr' ], [ -ENOSYS() ] ); return; } sub listxattr { my ( $self, $context, $path ) = @_; # we don't have any extended attribute support $poe_kernel->yield( 'reply', [ 'listxattr' ], [ 0 ] ); return; } sub removexattr { my ( $self, $context, $path, $attr ) = @_; # we don't have any extended attribute support $poe_kernel->yield( 'reply', [ 'removexattr' ], [ -ENOSYS() ] ); return; } sub open { my ( $self, $context, $path, $flags ) = @_; # make a special wrapper my $cb = $poe_kernel->get_active_session->postback( 'reply', 'open', $context ); my $callback = sub { my $fh = shift; if ( defined $fh ) { if ( openhandle( $fh ) ) { # arg, generate our own ID my $id = 0; 1 while exists $self->{'fhmap'}->{ ++$id }; $self->{'fhmap'}->{ $id } = $fh; $context->{'fh'} = $id; $cb->( 0 ); } else { # error code $cb->( $fh ); } } else { $cb->( -EIO() ); } }; # figure out the mode and set it # FIXME we use 0666 as default, should we change this or something? my $mode = 0; if ( $flags & O_CREAT ) { $mode = oct( 666 ); } # actually open it! $self->fsv->open( $path, $flags, $mode, $callback ); return; } sub read { my ( $self, $context, $path, $len, $offset ) = @_; # get the fh my $fh = undef; if ( exists $self->{'fhmap'}->{ $context->{'fh'} } ) { $fh = $self->{'fhmap'}->{ $context->{'fh'} }; } if ( ! defined $fh ) { $poe_kernel->yield( 'reply', [ 'read' ], [ -EINVAL() ] ); } else { # make our custom wrapper my $cb = $poe_kernel->get_active_session->postback( 'reply', 'read' ); my $buf = ''; my $callback = sub { if ( ! defined $_[0] ) { $cb->( -EIO() ); } else { $cb->( $buf ); } }; # aio_read $fh,$offset,$length, $data,$dataoffset, $callback->($retval) $self->fsv->read( $fh, $offset, $len, $buf, 0, $callback ); } return; } sub flush { my ( $self, $context, $path ) = @_; # get the fh my $fh = undef; if ( exists $self->{'fhmap'}->{ $context->{'fh'} } ) { $fh = $self->{'fhmap'}->{ $context->{'fh'} }; } if ( ! defined $fh ) { $poe_kernel->yield( 'reply', [ 'flush' ], [ -EINVAL() ] ); } else { # we map flush to fsync and hope all will be well :) $self->fsv->fsync( $fh, $self->_cb( 'flush' ) ); } return; } sub release { my ( $self, $context, $path, $flags ) = @_; # get the fh my $fh = undef; if ( exists $self->{'fhmap'}->{ $context->{'fh'} } ) { $fh = $self->{'fhmap'}->{ $context->{'fh'} }; } # FUSE sometimes calls release multiple times per file if ( ! defined $fh ) { # assume success $poe_kernel->yield( 'reply', [ 'release' ], [ 0 ] ); } else { # make our custom wrapper my $cb = $poe_kernel->get_active_session->postback( 'reply', 'release' ); my $callback = sub { # IO::AIO returns 0 as successful close... if ( $_[0] == 0 ) { # get rid of our mapping if ( exists $self->{'fhmap'}->{ $context->{'fh'} } ) { delete $self->{'fhmap'}->{ $context->{'fh'} }; } # FUSE needs 0 as retval $cb->( 0 ); } else { $cb->( $_[0] ); } }; $self->fsv->close( $fh, $callback ); } return; } sub truncate { my ( $self, $context, $path, $offset ) = @_; $self->fsv->truncate( $path, $offset, $self->_cb( 'truncate' ) ); return; } sub write { my ( $self, $context, $path, $buffer, $offset ) = @_; # get the fh my $fh = undef; if ( exists $self->{'fhmap'}->{ $context->{'fh'} } ) { $fh = $self->{'fhmap'}->{ $context->{'fh'} }; } if ( ! defined $fh ) { $poe_kernel->yield( 'reply', [ 'write' ], [ -EINVAL() ] ); } else { # make a special wrapper my $cb = $poe_kernel->get_active_session->postback( 'reply', 'write' ); my $callback = sub { if ( defined $_[0] ) { $cb->( $_[0] ); } else { # error $cb->( -EIO() ); } }; # aio_write $fh,$offset,$length, $data,$dataoffset, $callback->($retval) $self->fsv->write( $fh, $offset, length( $buffer ), $buffer, 0, $callback ); } return; } sub mknod { my ( $self, $context, $path, $modes, $device ) = @_; $self->fsv->mknod( $path, $modes, $device, $self->_cb( 'mknod' ) ); return; } sub mkdir { my ( $self, $context, $path, $modes ) = @_; $self->fsv->mkdir( $path, $modes, $self->_cb( 'mkdir' ) ); return; } sub unlink { my ( $self, $context, $path ) = @_; $self->fsv->unlink( $path, $self->_cb( 'unlink' ) ); return; } sub rmdir { my ( $self, $context, $path ) = @_; $self->fsv->rmdir( $path, $self->_cb( 'rmdir' ) ); return; } sub symlink { my ( $self, $context, $path, $symlink ) = @_; $self->fsv->symlink( $path, $symlink, $self->_cb( 'symlink' ) ); return; } sub rename { my ( $self, $context, $path, $newpath ) = @_; $self->fsv->rename( $path, $newpath, $self->_cb( 'rename' ) ); return; } sub link { my ( $self, $context, $path, $hardlink ) = @_; $self->fsv->link( $path, $hardlink, $self->_cb( 'link' ) ); return; } sub chmod { my ( $self, $context, $path, $modes ) = @_; $self->fsv->chmod( $path, $modes, $self->_cb( 'chmod' ) ); return; } sub chown { my ( $self, $context, $path, $uid, $gid ) = @_; $self->fsv->chown( $path, $uid, $gid, $self->_cb( 'chown' ) ); return; } sub utime { my ( $self, $context, $path, $atime, $mtime ) = @_; $self->fsv->utime( $path, $atime, $mtime, $self->_cb( 'utime' ) ); return; } sub fsync { my ( $self, $context, $path, $fsync_mode ) = @_; # get the fh my $fh = undef; if ( exists $self->{'fhmap'}->{ $context->{'fh'} } ) { $fh = $self->{'fhmap'}->{ $context->{'fh'} }; } if ( ! defined $fh ) { $poe_kernel->yield( 'reply', [ 'fsync' ], [ -EINVAL() ] ); } else { # interesting, ::Async don't care about $fsync_mode... $self->fsv->fsync( $fh, $self->_cb( 'fsync' ) ); } return; } sub statfs { my( $self, $context ) = @_; # FIXME fake the data... # $namelen, $files, $files_free, $blocks, $blocks_avail, $blocksize $poe_kernel->yield( 'reply', [ 'statfs' ], [ 255, 1, 1, 1, 1, 2 ] ); return; } 1; __END__ =head1 NAME POE::Component::Fuse::AsyncFsV - Wrapper for Filesys::Virtual::Async =head1 SYNOPSIS Please do not use this module directly. =head1 ABSTRACT Please do not use this module directly. =head1 DESCRIPTION This module is responsible for "wrapping" L objects and making them communicate properly with the FUSE API that L exposes. Please do not use this module directly. =head1 EXPORT None. =head1 SEE ALSO L L =head1 AUTHOR Apocalypse Eapocal@cpan.orgE Props goes to xantus who wrote the L module! Inspiration was taken from the L module too. =head1 COPYRIGHT AND LICENSE Copyright 2009 by Apocalypse This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut