use Time::HiRes qw(sleep);
use Getopt::Long;
+# $fsel_open_mask is used to limit the number of opens to 1 per file. This
+# uses the file index (0-F) as $fh, as poll support requires a unique handle
+# per open file. Lifting this would require more complete open file
+# management.
+my $fsel_open_mask :shared = 0;
+
+# Maximum "file" size.
use constant FSEL_CNT_MAX => 10;
use constant FSEL_FILES => 16;
-my $fsel_open_mask :shared = 0;
+# Used only as a lock for $fsel_poll_notify_mask and @fsel_cnt.
+my $fsel_mutex :shared;
+# Mask indicating what FDs have poll notifications waiting.
my $fsel_poll_notify_mask :shared = 0;
+# Poll notification handles.
my @fsel_poll_handle :shared;
+# Number of bytes for each "file".
my @fsel_cnt :shared;
+# Initialize all byte counts.
map { $fsel_cnt[$_] = 0 } (0 .. (FSEL_FILES - 1));
-my $fsel_mutex :shared;
-
sub fsel_path_index {
my ($path) = @_;
print 'called ', (caller(0))[3], "\n";
- my $ch = substr($path, 1, 1);
- if (length($path) != 2 || substr($path, 0, 1) ne '/' ||
- $ch !~ /^[0-9A-F]$/) {
- return -1;
- }
- return hex($ch);
+ return -1 if $path !~ m{^/([0-9A-F])$};
+ return hex($1);
}
sub fsel_getattr {
my @stbuf = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
if ($path eq '/') {
- $stbuf[2] = S_IFDIR | 0555;
- $stbuf[3] = 2;
+ @stbuf[2, 3] = (S_IFDIR | 0555, 2);
return @stbuf;
}
my $idx = fsel_path_index($path);
return -&ENOENT if $idx < 0;
- $stbuf[2] = S_IFREG | 0444;
- $stbuf[3] = 1;
- $stbuf[7] = $fsel_cnt[$idx];
+ @stbuf[2, 3, 7] = (S_IFREG | 0444, 1, $fsel_cnt[$idx]);
return @stbuf;
}
my $idx = fsel_path_index($path);
return -&ENOENT if $idx < 0;
- return -&EACCES if $flags & 3 != O_RDONLY;
- return -&EBUSY if $fsel_open_mask & (1 << $idx);
+ return -&EACCES if $flags & O_ACCMODE != O_RDONLY;
+ return -&EBUSY if $fsel_open_mask & (1 << $idx);
$fsel_open_mask |= (1 << $idx);
- $info->{'direct_io'} = 1;
- $info->{'nonseekable'} = 1;
- my $foo = [ $idx + 0 ];
- return (0, $foo->[0]);
+ # fsel files are nonseekable somewhat pipe-like files which get filled
+ # up periodically by the producer thread, and consumed on read. Tell
+ # FUSE to do this right.
+ @{$info}{'direct_io', 'nonseekable'} = (1, 1);
+ return (0, $idx);
}
sub fsel_release {
my ($path, $flags, $fh) = @_;
print 'called ', (caller(0))[3], "\n";
- ## HACK
- #$fh = fsel_path_index($path);
$fsel_open_mask &= ~(1 << $fh);
return 0;
sub fsel_read {
my ($path, $size, $offset, $fh) = @_;
print 'called ', (caller(0))[3], "\n";
- ## HACK
- #$fh = fsel_path_index($path);
lock($fsel_mutex);
if ($fsel_cnt[$fh] < $size) {
sub fsel_poll {
my ($path, $ph, $revents, $fh) = @_;
print 'called ', (caller(0))[3], ", path = \"$path\", fh = $fh, revents = $revents\n";
- ## HACK
- #$fh = fsel_path_index($path);
lock($fsel_mutex);
if ($ph) {
my $oldph = $fsel_poll_handle[$fh];
- if ($oldph) {
- pollhandle_destroy($oldph);
- }
+ pollhandle_destroy($oldph) if $oldph;
$fsel_poll_notify_mask |= (1 << $fh);
$fsel_poll_handle[$fh] = $ph;
}
sub fsel_producer {
print 'called ', (caller(0))[3], "\n";
local $SIG{'KILL'} = sub { threads->exit(); };
- my $tv = 0.25;
- my $idx = 0;
- my $nr = 1;
+ my ($tv, $idx, $nr) = (0.25, 0, 1);
while (1) {
{
my ($i, $t);
lock($fsel_mutex);
+ # This is the main producer loop which is executed every 250
+ # msec. On each iteration, it adds one byte to 1, 2 or 4 files
+ # and sends a poll notification if a poll handle is present.
for (($i, $t) = (0, $idx); $i < $nr; $i++,
$t = (($t + int(FSEL_FILES / $nr)) % FSEL_FILES)) {
next if $fsel_cnt[$t] == FSEL_CNT_MAX;
my $ph = $fsel_poll_handle[$t];
notify_poll($ph);
pollhandle_destroy($ph);
- $fsel_poll_handle[$t] = undef;
$fsel_poll_notify_mask &= ~(1 << $t);
+ $fsel_poll_handle[$t] = undef;
}
}
$idx = ($idx + 1) % FSEL_FILES;
if ($idx == 0) {
+ # Cycle through 1, 2 and 4.
$nr = ($nr * 2) % 7;
}
}
croak("Fuse doesn't have poll") unless Fuse::fuse_version() >= 2.8;
my %fuseargs = (
- 'mountpoint' => $ARGV[0],
'getattr' => 'main::fsel_getattr',
'readdir' => 'main::fsel_readdir',
'open' => 'main::fsel_open',
}
) || croak("Malformed options passed");
+$fuseargs{'mountpoint'} = $ARGV[0];
+
my $thread = threads->create(\&fsel_producer);
Fuse::main(%fuseargs);