Lion compat for -lfuse_ino64 hack.
[perl-fuse.git] / examples / fsel.pl
1 #!/usr/bin/env perl
2
3 use strict;
4 no strict qw(refs);
5
6 use threads;
7 use threads::shared;
8
9 use Carp;
10 local $SIG{'__WARN__'} = \&Carp::cluck;
11
12 use Fuse qw(:all);
13 use Fcntl qw(:mode);
14 use POSIX;
15 use IO::Poll qw(POLLIN);
16 use Time::HiRes qw(sleep);
17 use Getopt::Long;
18
19 # $fsel_open_mask is used to limit the number of opens to 1 per file. This
20 # uses the file index (0-F) as $fh, as poll support requires a unique handle
21 # per open file. Lifting this would require more complete open file
22 # management.
23 my $fsel_open_mask :shared = 0;
24
25 # Maximum "file" size.
26 use constant FSEL_CNT_MAX   => 10;
27 use constant FSEL_FILES     => 16;
28
29 # Used only as a lock for $fsel_poll_notify_mask and @fsel_cnt.
30 my $fsel_mutex :shared;
31 # Mask indicating what FDs have poll notifications waiting.
32 my $fsel_poll_notify_mask :shared = 0;
33 # Poll notification handles.
34 my @fsel_poll_handle :shared;
35 # Number of bytes for each "file".
36 my @fsel_cnt :shared;
37 # Initialize all byte counts.
38 map { $fsel_cnt[$_] = 0 } (0 .. (FSEL_FILES - 1));
39
40 sub fsel_path_index {
41     my ($path) = @_;
42     print 'called ', (caller(0))[3], "\n";
43
44     return -1 if $path !~ m{^/([0-9A-F])$};
45     return hex($1);
46 }
47
48 sub fsel_getattr {
49     my ($path) = @_;
50     print 'called ', (caller(0))[3], "\n";
51     my @stbuf = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
52
53     if ($path eq '/') {
54         @stbuf[2, 3] = (S_IFDIR | 0555, 2);
55         return @stbuf;
56     }
57
58     my $idx = fsel_path_index($path);
59     return -&ENOENT if $idx < 0;
60
61     @stbuf[2, 3, 7] = (S_IFREG | 0444, 1, $fsel_cnt[$idx]);
62     return @stbuf;
63 }
64
65 sub fsel_readdir {
66     my ($path, $offset) = @_;
67     print 'called ', (caller(0))[3], "\n";
68
69     return -&ENOENT if $path ne '/';
70
71     return('.', '..', map { sprintf('%X', $_) } (0 .. (FSEL_FILES - 1)), 0);
72 }
73
74 sub fsel_open {
75     my ($path, $flags, $info) = @_;
76     print 'called ', (caller(0))[3], "\n";
77
78     my $idx = fsel_path_index($path);
79     return -&ENOENT if $idx < 0;
80     return -&EACCES if $flags & O_ACCMODE != O_RDONLY;
81     return -&EBUSY  if $fsel_open_mask & (1 << $idx);
82     $fsel_open_mask |= (1 << $idx);
83
84     # fsel files are nonseekable somewhat pipe-like files which get filled
85     # up periodically by the producer thread, and consumed on read. Tell
86     # FUSE to do this right.
87     @{$info}{'direct_io', 'nonseekable'} = (1, 1);
88     return (0, $idx);
89 }
90
91 sub fsel_release {
92     my ($path, $flags, $fh) = @_;
93     print 'called ', (caller(0))[3], "\n";
94
95     $fsel_open_mask &= ~(1 << $fh);
96     return 0;
97 }
98
99 sub fsel_read {
100     my ($path, $size, $offset, $fh) = @_;
101     print 'called ', (caller(0))[3], "\n";
102     lock($fsel_mutex);
103
104     if ($fsel_cnt[$fh] < $size) {
105         $size = $fsel_cnt[$fh];
106     }
107     printf("READ   \%X transferred=\%u cnt=\%u\n", $fh, $size, $fsel_cnt[$fh]);
108     $fsel_cnt[$fh] -= $size;
109
110     return(chr($fh) x $size);
111 }
112
113 our $polled_zero :shared = 0;
114
115 sub fsel_poll {
116     my ($path, $ph, $revents, $fh) = @_;
117     print 'called ', (caller(0))[3], ", path = \"$path\", fh = $fh, revents = $revents\n";
118
119     lock($fsel_mutex);
120
121     if ($ph) {
122         my $oldph = $fsel_poll_handle[$fh];
123         pollhandle_destroy($oldph) if $oldph;
124         $fsel_poll_notify_mask |= (1 << $fh);
125         $fsel_poll_handle[$fh] = $ph;
126     }
127
128     if ($fsel_cnt[$fh]) {
129         $revents |= POLLIN;
130         printf("POLL   \%X cnt=\%u polled_zero=\%u\n", $fh, $fsel_cnt[$fh],
131                 $polled_zero);
132         $polled_zero = 0;
133     }
134     else {
135         $polled_zero++;
136     }
137
138     return(0, $revents);
139 }
140
141 sub fsel_producer {
142     print 'called ', (caller(0))[3], "\n";
143     local $SIG{'KILL'} = sub { threads->exit(); };
144     my ($tv, $idx, $nr) = (0.25, 0, 1);
145
146     while (1) {
147         {
148             my ($i, $t);
149             lock($fsel_mutex);
150
151             # This is the main producer loop which is executed every 250
152             # msec. On each iteration, it adds one byte to 1, 2 or 4 files
153             # and sends a poll notification if a poll handle is present.
154             for (($i, $t) = (0, $idx); $i < $nr; $i++,
155                     $t = (($t + int(FSEL_FILES / $nr)) % FSEL_FILES)) {
156                 next if $fsel_cnt[$t] == FSEL_CNT_MAX;
157
158                 $fsel_cnt[$t]++;
159                 if ($fsel_poll_notify_mask & (1 << $t)) {
160                     printf("NOTIFY \%X\n", $t);
161                     my $ph = $fsel_poll_handle[$t];
162                     notify_poll($ph);
163                     pollhandle_destroy($ph);
164                     $fsel_poll_notify_mask &= ~(1 << $t);
165                     $fsel_poll_handle[$t] = undef;
166                 }
167             }
168
169             $idx = ($idx + 1) % FSEL_FILES;
170             if ($idx == 0) {
171                 # Cycle through 1, 2 and 4.
172                 $nr = ($nr * 2) % 7;
173             }
174         }
175
176         sleep($tv);
177     }
178 }
179
180 croak("Fuse doesn't have poll") unless Fuse::fuse_version() >= 2.8;
181
182 my %fuseargs = (
183     'getattr'   => 'main::fsel_getattr',
184     'readdir'   => 'main::fsel_readdir',
185     'open'      => 'main::fsel_open',
186     'release'   => 'main::fsel_release',
187     'read'      => 'main::fsel_read',
188     'poll'      => 'main::fsel_poll',
189 );
190
191 GetOptions(
192     'use-threads'       => sub {
193         print STDERR "Warning: Fuse currently has bugs related to threading which may cause misbehavior\n";
194         $fuseargs{'threaded'} = 1;
195     },
196     'debug'             => sub {
197         $fuseargs{'debug'} = 1;
198     }
199 ) || croak("Malformed options passed");
200
201 $fuseargs{'mountpoint'} = $ARGV[0];
202
203 my $thread = threads->create(\&fsel_producer);
204
205 Fuse::main(%fuseargs);
206
207 $thread->kill('KILL');
208 $thread->join();