Fuse::Class 0.02
@@ -0,0 +1,735 @@ | ||
1 | +# | |
2 | +# test filesystem for Fuse 2.8 | |
3 | +# (file operations are done via file/directory handle.) | |
4 | +# | |
5 | + | |
6 | +use strict; | |
7 | +use warnings; | |
8 | + | |
9 | +use Fcntl; | |
10 | +use Errno; | |
11 | + | |
12 | +package test::fuse28; | |
13 | + | |
14 | +use base qw(Fuse::Class); | |
15 | + | |
16 | +sub new { | |
17 | + my $class = shift; | |
18 | + | |
19 | + my $self = { | |
20 | + root => test::fuse28::Directory->new, | |
21 | + | |
22 | + handle => { | |
23 | + } | |
24 | + }; | |
25 | + | |
26 | + return bless $self, $class; | |
27 | +} | |
28 | + | |
29 | +sub issue_handle { | |
30 | + my $self = shift; | |
31 | + my $obj = shift; | |
32 | + | |
33 | + my $i = 0; | |
34 | + while ($self->{handle}->{$i}) { | |
35 | + $i++; | |
36 | + } | |
37 | + | |
38 | + $self->{handle}->{$i} = $obj; | |
39 | + | |
40 | + return $i; | |
41 | +} | |
42 | + | |
43 | +sub release_handle { | |
44 | + my $self = shift; | |
45 | + my ($fh) = @_; | |
46 | + | |
47 | + delete $self->{handle}->{$fh}; | |
48 | +} | |
49 | + | |
50 | +sub pickup { | |
51 | + my $self = shift; | |
52 | + my $path = shift; | |
53 | + | |
54 | + my $ret = $self->{root}; | |
55 | + | |
56 | + for my $e (split('/', $path)) { | |
57 | + next if ($e eq ''); | |
58 | + | |
59 | + if ($ret->isa('test::fuse28::Directory')) { | |
60 | + if ($e eq '..') { | |
61 | + $ret = $ret->parent; | |
62 | + } | |
63 | + elsif ($e eq '.') { | |
64 | + ; # nothing | |
65 | + } | |
66 | + else { | |
67 | + $ret = $ret->entity($e); | |
68 | + } | |
69 | + } | |
70 | + else { | |
71 | + return undef; | |
72 | + } | |
73 | + } | |
74 | + | |
75 | + return $ret; | |
76 | +} | |
77 | + | |
78 | +sub init { | |
79 | + # print STDERR "perl28.pm is started\n"; | |
80 | + return "perl28"; | |
81 | +} | |
82 | + | |
83 | +# I don't know when this method is called...? | |
84 | +sub destroy { | |
85 | + my $param = shift; | |
86 | + print STDERR "$param is ended\n"; | |
87 | +} | |
88 | + | |
89 | +sub fgetattr { | |
90 | + my $self = shift; | |
91 | + my ($path, $fh) = @_; | |
92 | + | |
93 | + my $entity = $self->{handle}->{$fh}; | |
94 | + return (-2) unless ($entity); | |
95 | + return (-1) unless ($entity->can('attr')); | |
96 | + | |
97 | + $entity->attr; | |
98 | +} | |
99 | + | |
100 | +sub getattr { | |
101 | + my $self = shift; | |
102 | + my ($path) = @_; | |
103 | + | |
104 | + my $entity = $self->pickup($path); | |
105 | + return -2 unless ($entity); | |
106 | + | |
107 | + return $entity->attr; | |
108 | +} | |
109 | + | |
110 | +sub readlink { | |
111 | + my $self = shift; | |
112 | + my ($path) = @_; | |
113 | + | |
114 | + my $entity = $self->pickup($path); | |
115 | + return -2 unless ($entity); | |
116 | + return -1 unless ($entity->can('readlink')); | |
117 | + | |
118 | + return $entity->readlink; | |
119 | +} | |
120 | + | |
121 | +sub getdir { | |
122 | + my $self = shift; | |
123 | + my ($path) = @_; | |
124 | + | |
125 | + # die "this function must not be called."; | |
126 | + return -1; | |
127 | +} | |
128 | + | |
129 | +sub mknod { | |
130 | + my $self = shift; | |
131 | + my ($path, $mode, $devno) = @_; | |
132 | + | |
133 | + my ($dirname, $name) = ($path =~ m/^(.*)\/([^\/]+)$/); | |
134 | + return -2 unless (defined($dirname) && defined($name)); # badname ? | |
135 | + | |
136 | + my $dir = $self->pickup($dirname); | |
137 | + return -2 unless ($dir); | |
138 | + | |
139 | + return $dir->mknod($name, $mode, $devno); | |
140 | +} | |
141 | + | |
142 | +sub mkdir { | |
143 | + my $self = shift; | |
144 | + my ($path, $mode) = @_; | |
145 | + | |
146 | + my ($dirname, $name) = ($path =~ m/^(.*)\/([^\/]+)$/); | |
147 | + return -2 unless (defined($dirname) && defined($name)); # badname ? | |
148 | + | |
149 | + my $dir = $self->pickup($dirname); | |
150 | + return -2 unless ($dir); | |
151 | + | |
152 | + return $dir->mkdir($name, $mode); | |
153 | +} | |
154 | + | |
155 | +sub unlink { | |
156 | + my $self = shift; | |
157 | + my ($path) = @_; | |
158 | + | |
159 | + my ($dirname, $name) = ($path =~ m/^(.*)\/([^\/]+)$/); | |
160 | + return -2 unless (defined($dirname) && defined($name)); # badname ? | |
161 | + | |
162 | + my $dir = $self->pickup($dirname); | |
163 | + return -2 unless ($dir); | |
164 | + | |
165 | + return $dir->unlink($name); | |
166 | +} | |
167 | + | |
168 | +sub rmdir { | |
169 | + my $self = shift; | |
170 | + my ($path) = @_; | |
171 | + | |
172 | + my ($dirname, $name) = ($path =~ m/^(.*)\/([^\/]+)$/); | |
173 | + return -2 unless (defined($dirname) && defined($name)); # badname ? | |
174 | + | |
175 | + my $dir = $self->pickup($dirname); | |
176 | + return -2 unless ($dir); | |
177 | + | |
178 | + return $dir->rmdir($name); | |
179 | +} | |
180 | + | |
181 | +sub symlink { | |
182 | + my $self = shift; | |
183 | + my ($existing, $symlink) = @_; | |
184 | + | |
185 | + my ($dirname, $name) = ($symlink =~ m/^(.*)\/([^\/]+)$/); | |
186 | + return -2 unless (defined($dirname) && defined($name)); # badname ? | |
187 | + | |
188 | + my $dir = $self->pickup($dirname); | |
189 | + return -2 unless ($dir); | |
190 | + | |
191 | + return $dir->symlink($name, $existing); | |
192 | +} | |
193 | + | |
194 | +sub rename { | |
195 | + my $self = shift; | |
196 | + my ($old_name, $new_name) = @_; | |
197 | + | |
198 | + my ($dirname1, $name1) = ($old_name =~ m/^(.*)\/([^\/]+)$/); | |
199 | + return -2 unless (defined($dirname1) && defined($name1)); # badname ? | |
200 | + | |
201 | + my ($dirname2, $name2) = ($new_name =~ m/^(.*)\/([^\/]+)$/); | |
202 | + return -2 unless (defined($dirname2) && defined($name2)); # badname ? | |
203 | + | |
204 | + my $dir1 = $self->pickup($dirname1); | |
205 | + return -2 unless ($dir1); | |
206 | + | |
207 | + my $dir2 = $self->pickup($dirname2); | |
208 | + return -2 unless ($dir2); | |
209 | + | |
210 | + return $dir1->rename($name1, $dir2, $name2); | |
211 | +} | |
212 | + | |
213 | +sub opendir { | |
214 | + my $self = shift; | |
215 | + my ($path) = @_; | |
216 | + | |
217 | + my $entity = $self->pickup($path); | |
218 | + return (-2) unless ($entity); | |
219 | + | |
220 | + if ($entity->isa('test::fuse28::Directory')) { | |
221 | + my $fh = $self->issue_handle($entity); | |
222 | + return (0, $fh); | |
223 | + } | |
224 | + else { | |
225 | + return (-2); | |
226 | + } | |
227 | +} | |
228 | + | |
229 | +sub readdir { | |
230 | + my $self = shift; | |
231 | + my ($path, $offset, $dh) = @_; | |
232 | + | |
233 | + if ($path eq '/test/readdir-type-1') { | |
234 | + return $self->readdir_test_type_1(@_); | |
235 | + } | |
236 | + elsif ($path eq '/test/readdir-type-2') { | |
237 | + return $self->readdir_test_type_2(@_); | |
238 | + } | |
239 | + | |
240 | + my $dir = $self->{handle}->{$dh}; | |
241 | + return (-2) unless ($dir); | |
242 | + | |
243 | + my @names = $dir->readdir; | |
244 | + | |
245 | + if ($offset < $#names) { | |
246 | + return (@names[$offset..$#names], 0); | |
247 | + } | |
248 | + | |
249 | + return (0); | |
250 | +} | |
251 | + | |
252 | +sub readdir_test_type_1 { | |
253 | + my $self = shift; | |
254 | + my ($path, $offset, $dh) = @_; | |
255 | + | |
256 | + my $dir = $self->{handle}->{$dh}; | |
257 | + return (-2) unless ($dir); | |
258 | + | |
259 | + # print STDERR "readdir_test_type_1, path=$path, offset=$offset\n"; | |
260 | + | |
261 | + my $i = 1; | |
262 | + my @list; | |
263 | + | |
264 | + foreach my $name ($dir->readdir) { | |
265 | + push(@list, [$i++, $name]); | |
266 | + } | |
267 | + | |
268 | + if ($offset < $#list) { | |
269 | + return (@list[$offset..$#list], 0); | |
270 | + } | |
271 | + | |
272 | + return (0); | |
273 | +} | |
274 | + | |
275 | +sub readdir_test_type_2 { | |
276 | + my $self = shift; | |
277 | + my ($path, $offset, $dh) = @_; | |
278 | + | |
279 | + my $dir = $self->{handle}->{$dh}; | |
280 | + return (-2) unless ($dir); | |
281 | + | |
282 | + # print STDERR "readdir_test_type_2, path=$path, offset=$offset\n"; | |
283 | + | |
284 | + my $i = 1; | |
285 | + my @list; | |
286 | + | |
287 | + foreach my $name ($dir->readdir) { | |
288 | + my $entity = $self->pickup("$path/$name"); | |
289 | + next unless ($entity); | |
290 | + | |
291 | + push(@list, [$i++, $name, [$entity->attr]]); | |
292 | + } | |
293 | + | |
294 | + if ($offset < $#list) { | |
295 | + return (@list[$offset..$#list], 0); | |
296 | + } | |
297 | + | |
298 | + return (0); | |
299 | +} | |
300 | + | |
301 | +sub releasedir { | |
302 | + my $self = shift; | |
303 | + my ($path, $dh) = @_; | |
304 | + | |
305 | + if ($self->{handle}->{$dh}) { | |
306 | + $self->release_handle($dh); | |
307 | + return 0; | |
308 | + } | |
309 | + | |
310 | + return -2; | |
311 | +} | |
312 | + | |
313 | +sub chmod { | |
314 | + my $self = shift; | |
315 | + my ($path, $modes) = @_; | |
316 | + | |
317 | + my $entity = $self->pickup($path); | |
318 | + return -2 unless ($entity); | |
319 | + | |
320 | + $entity->chmod($modes); | |
321 | +} | |
322 | + | |
323 | +sub chown { | |
324 | + my $self = shift; | |
325 | + my ($path, $uid, $gid) = @_; | |
326 | + | |
327 | + my $entity = $self->pickup($path); | |
328 | + return -2 unless ($entity); | |
329 | + | |
330 | + $entity->chown($uid, $gid); | |
331 | +} | |
332 | + | |
333 | +sub ftruncate { | |
334 | + my $self = shift; | |
335 | + my ($path, $offset, $fh) = @_; | |
336 | + | |
337 | + my $entity = $self->{handle}->{$fh}; | |
338 | + return (-2) unless ($entity); | |
339 | + return (-1) unless ($entity->can('truncate')); | |
340 | + | |
341 | + $entity->truncate($offset); | |
342 | +} | |
343 | + | |
344 | +sub truncate { | |
345 | + my $self = shift; | |
346 | + my ($path, $offset) = @_; | |
347 | + | |
348 | + my $entity = $self->pickup($path); | |
349 | + return -2 unless ($entity); | |
350 | + return -1 unless ($entity->can('truncate')); | |
351 | + | |
352 | + $entity->truncate($offset); | |
353 | +} | |
354 | + | |
355 | +sub utime { | |
356 | + my $self = shift; | |
357 | + my ($path, $atime, $mtime) = @_; | |
358 | + | |
359 | + # die "utimens must be called"; | |
360 | + return -1; | |
361 | +} | |
362 | + | |
363 | +sub open { | |
364 | + my $self = shift; | |
365 | + my ($path, $flags, $fileinfo) = @_; | |
366 | + | |
367 | + my $entity = $self->pickup($path); | |
368 | + return (-2) unless ($entity); | |
369 | + | |
370 | + return (0, $self->issue_handle($entity)); | |
371 | +} | |
372 | + | |
373 | +sub write { | |
374 | + my $self = shift; | |
375 | + my ($path, $buffer, $offset, $fh) = @_; | |
376 | + | |
377 | + my $entity = $self->{handle}->{$fh}; | |
378 | + return (-2) unless ($entity); | |
379 | + return (-1) unless ($entity->can('write')); | |
380 | + | |
381 | + $entity->write($buffer, $offset); | |
382 | +} | |
383 | + | |
384 | +sub read { | |
385 | + my $self = shift; | |
386 | + my ($path, $size, $offset, $fh) = @_; | |
387 | + | |
388 | + my $entity = $self->{handle}->{$fh}; | |
389 | + return (-2) unless ($entity); | |
390 | + return (-1) unless ($entity->can('read')); | |
391 | + | |
392 | + $entity->read($size, $offset); | |
393 | +} | |
394 | + | |
395 | +sub statfs { | |
396 | + my $self = shift; | |
397 | + | |
398 | + return (255, 50000, 40000, 30000, 20000, 10000); | |
399 | +} | |
400 | + | |
401 | +sub utimens { | |
402 | + my $self = shift; | |
403 | + my ($path, $atime, $mtime) = @_; | |
404 | + | |
405 | + my $entity = $self->pickup($path); | |
406 | + return -2 unless ($entity); | |
407 | + | |
408 | + return $entity->utimens($atime, $mtime); | |
409 | +} | |
410 | + | |
411 | +sub access { | |
412 | + my $self = shift; | |
413 | + my ($path, $mode) = @_; | |
414 | + | |
415 | + if ($path eq '/test/access_no_perm') { | |
416 | + # if exsits, it's not accesible!! | |
417 | + return -Errno::EPERM() if $self->pickup($path); | |
418 | + } | |
419 | + | |
420 | + return 0; | |
421 | +} | |
422 | + | |
423 | +sub create { | |
424 | + my $self = shift; | |
425 | + my ($path, $mask, $mode) = @_; | |
426 | + my ($dirname, $name) = ($path =~ m/^(.*)\/([^\/]+)$/); | |
427 | + return -2 unless (defined($dirname) && defined($name)); # badname ? | |
428 | + | |
429 | + my $dir = $self->pickup($dirname); | |
430 | + return -2 unless ($dir); | |
431 | + | |
432 | + return Errno::EXISTS if ($self->pickup($path)); | |
433 | + | |
434 | + my $ret = $dir->mknod($name, $mask, 0); | |
435 | + return ($ret) if ($ret != 0); | |
436 | + | |
437 | + my $entity = $self->pickup($path); | |
438 | + return (-2) unless ($entity); | |
439 | + | |
440 | + return (0, $self->issue_handle($entity)); | |
441 | +} | |
442 | + | |
443 | +package test::fuse28::Entity; | |
444 | + | |
445 | +my $last_ino = 0; | |
446 | + | |
447 | +sub new { | |
448 | + my $class = shift; | |
449 | + | |
450 | + my $t = time; | |
451 | + | |
452 | + my $self = { | |
453 | + # ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size, | |
454 | + # $atime,$mtime,$ctime,$blksize,$blocks) | |
455 | + attr => [0, $last_ino++, 0, 1, $>+0, $)+0, 0, 0, | |
456 | + $t, $t, $t, 1024, 0], | |
457 | + }; | |
458 | + | |
459 | + $self->{attr}->[8] = $t; | |
460 | + $self->{attr}->[9] = $t; | |
461 | + $self->{attr}->[10] = $t; | |
462 | + | |
463 | + bless $self, $class; | |
464 | +} | |
465 | + | |
466 | +sub attr { | |
467 | + my $self = shift; | |
468 | + return @{$self->{attr}}; | |
469 | +} | |
470 | + | |
471 | +sub chmod { | |
472 | + my $self = shift; | |
473 | + my ($modes) = @_; | |
474 | + | |
475 | + my $attr = $self->{attr}->[2] & ~(07777); | |
476 | + $self->{attr}->[2] = $attr | $modes; | |
477 | + | |
478 | + return 0; | |
479 | +} | |
480 | + | |
481 | +sub utimens { | |
482 | + my $self = shift; | |
483 | + my ($atime, $mtime) = @_; | |
484 | + | |
485 | + my $attr = $self->{attr}; | |
486 | + $attr->[8] = $atime if ($atime >= 0); | |
487 | + $attr->[9] = $mtime if ($mtime >= 0); | |
488 | + | |
489 | + return 0; | |
490 | +} | |
491 | + | |
492 | +sub chown { | |
493 | + my $self = shift; | |
494 | + my ($uid, $gid) = @_; | |
495 | + | |
496 | + $self->{attr}->[4] = $uid if ($uid >= 0); | |
497 | + $self->{attr}->[5] = $gid if ($gid >= 0); | |
498 | + | |
499 | + return 0; | |
500 | +} | |
501 | + | |
502 | +# | |
503 | +# Directory | |
504 | +# | |
505 | +package test::fuse28::Directory; | |
506 | + | |
507 | +use Fcntl qw(:mode); | |
508 | +use base qw(test::fuse28::Entity); | |
509 | +use Scalar::Util qw(weaken); | |
510 | + | |
511 | +sub new { | |
512 | + my $class = shift; | |
513 | + my $parent = shift; | |
514 | + | |
515 | + my $self = $class->SUPER::new; | |
516 | + $self->{attr}->[2] = S_IFDIR | S_IRWXU; | |
517 | + | |
518 | + if (!defined($parent)) { | |
519 | + $self->{parent} = $self; | |
520 | + } | |
521 | + else { | |
522 | + $self->{parent} = $parent; | |
523 | + } | |
524 | + | |
525 | + $self->{children} = {}; | |
526 | + | |
527 | + # avoid cyclic reference | |
528 | + weaken($self->{parent}); | |
529 | + | |
530 | + bless $self, $class; | |
531 | +} | |
532 | + | |
533 | +sub parent { | |
534 | + my $self = shift; | |
535 | + return $self->{parent}; | |
536 | +} | |
537 | + | |
538 | +sub entity { | |
539 | + my $self = shift; | |
540 | + my $name = shift; | |
541 | + | |
542 | + return $self if ($name eq '.'); | |
543 | + return $self->parent if ($name eq '..'); | |
544 | + | |
545 | + return $self->{children}->{$name}; | |
546 | +} | |
547 | + | |
548 | +sub readdir { | |
549 | + my $self = shift; | |
550 | + return ('..', '.', keys %{$self->{children}}); | |
551 | +} | |
552 | + | |
553 | +sub mknod { | |
554 | + my $self = shift; | |
555 | + my ($name, $mode, $devno) = @_; | |
556 | + | |
557 | + my $umask = 0; | |
558 | + $umask |= S_IRUSR if ($mode & 0400); | |
559 | + $umask |= S_IWUSR if ($mode & 0200); | |
560 | + $umask |= S_IXUSR if ($mode & 0100); | |
561 | + $umask |= S_IRGRP if ($mode & 0040); | |
562 | + $umask |= S_IWGRP if ($mode & 0020); | |
563 | + $umask |= S_IXGRP if ($mode & 0010); | |
564 | + $umask |= S_IROTH if ($mode & 0004); | |
565 | + $umask |= S_IWOTH if ($mode & 0002); | |
566 | + $umask |= S_IXOTH if ($mode & 0001); | |
567 | + | |
568 | + if (S_ISREG($mode)) { | |
569 | + my $newfile = test::fuse28::File->new; | |
570 | + my $attr = S_IFREG | $umask; | |
571 | + $newfile->{attr}->[2] = $attr; | |
572 | + $self->{children}->{$name} = $newfile; | |
573 | + return 0; | |
574 | + } | |
575 | + if (S_ISDIR($mode)) { | |
576 | + return $self->mkdir($name, $mode); | |
577 | + } | |
578 | + | |
579 | + if (S_ISLNK($mode)) { | |
580 | + return -1; | |
581 | + } | |
582 | + if (S_ISBLK($mode)) { | |
583 | + return -1; | |
584 | + } | |
585 | + if (S_ISCHR($mode)) { | |
586 | + return -1; | |
587 | + } | |
588 | + if (S_ISFIFO($mode)) { | |
589 | + return -1; | |
590 | + } | |
591 | + if (S_ISSOCK($mode)) { | |
592 | + return -1; | |
593 | + } | |
594 | + | |
595 | + return -1; | |
596 | +} | |
597 | + | |
598 | +sub mkdir { | |
599 | + my $self = shift; | |
600 | + my ($name, $mode) = @_; | |
601 | + | |
602 | + my $newdir = test::fuse28::Directory->new($self); | |
603 | + my $attr = S_IFDIR; | |
604 | + $attr |= S_IRUSR if ($mode & 0400); | |
605 | + $attr |= S_IWUSR if ($mode & 0200); | |
606 | + $attr |= S_IXUSR if ($mode & 0100); | |
607 | + $attr |= S_IRGRP if ($mode & 0040); | |
608 | + $attr |= S_IWGRP if ($mode & 0020); | |
609 | + $attr |= S_IXGRP if ($mode & 0010); | |
610 | + $attr |= S_IROTH if ($mode & 0004); | |
611 | + $attr |= S_IWOTH if ($mode & 0002); | |
612 | + $attr |= S_IXOTH if ($mode & 0001); | |
613 | + $newdir->{attr}->[2] = $attr; | |
614 | + | |
615 | + $self->{children}->{$name} = $newdir; | |
616 | + | |
617 | + return 0; | |
618 | +} | |
619 | + | |
620 | +sub unlink { | |
621 | + my $self = shift; | |
622 | + my ($name) = @_; | |
623 | + | |
624 | + my $entity = $self->{children}->{$name}; | |
625 | + return -2 unless ($entity); | |
626 | + delete $self->{children}->{$name}; | |
627 | + | |
628 | + return 0; | |
629 | +} | |
630 | + | |
631 | +sub rmdir { | |
632 | + my $self = shift; | |
633 | + my ($name) = @_; | |
634 | + | |
635 | + my $entity = $self->{children}->{$name}; | |
636 | + return -2 unless ($entity); | |
637 | + delete $self->{children}->{$name}; | |
638 | + | |
639 | + return 0; | |
640 | +} | |
641 | + | |
642 | +sub rename { | |
643 | + my $self = shift; | |
644 | + my ($old_name, $new_dir, $new_name) = @_; | |
645 | + | |
646 | + my $entity = $self->{children}->{$old_name}; | |
647 | + return -2 unless ($entity); | |
648 | + | |
649 | + delete $self->{children}->{$old_name}; | |
650 | + $new_dir->{children}->{$new_name} = $entity; | |
651 | + | |
652 | + return 0; | |
653 | +} | |
654 | + | |
655 | +sub symlink { | |
656 | + my $self = shift; | |
657 | + my ($name, $existing) = @_; | |
658 | + | |
659 | + my $link = test::fuse28::Symlink->new($existing); | |
660 | + my $attr = S_IFLNK | 0777; | |
661 | + $link->{attr}->[2] = $attr; | |
662 | + $self->{children}->{$name} = $link; | |
663 | + | |
664 | + return 0; | |
665 | +} | |
666 | + | |
667 | +# | |
668 | +# Normal File | |
669 | +# | |
670 | +package test::fuse28::File; | |
671 | + | |
672 | +use base qw(test::fuse28::Entity); | |
673 | + | |
674 | +sub new { | |
675 | + my $class = shift; | |
676 | + | |
677 | + my $self = $class->SUPER::new; | |
678 | + $self->{content} = ''; | |
679 | + | |
680 | + bless $self, $class; | |
681 | +} | |
682 | + | |
683 | +sub write { | |
684 | + my $self = shift; | |
685 | + my ($buffer, $offset) = @_; | |
686 | + | |
687 | + substr($self->{content}, $offset) = $buffer; | |
688 | + $self->{attr}->[7] = length($self->{content}); | |
689 | + $self->{attr}->[12] = int(($self->{attr}->[7] + $self->{attr}->[11] - 1) / $self->{attr}->[11]); | |
690 | + | |
691 | + return length($buffer); | |
692 | +} | |
693 | + | |
694 | +sub read { | |
695 | + my $self = shift; | |
696 | + my ($size, $offset) = @_; | |
697 | + | |
698 | + return substr($self->{content}, $offset, $size); | |
699 | +} | |
700 | + | |
701 | +sub truncate { | |
702 | + my $self = shift; | |
703 | + my ($offset) = @_; | |
704 | + | |
705 | + $self->{content} = substr($self->{content}, 0, $offset); | |
706 | + $self->{attr}->[7] = length($self->{content}); | |
707 | + | |
708 | + return 0; | |
709 | +} | |
710 | + | |
711 | +# | |
712 | +# Symlink | |
713 | +# | |
714 | +package test::fuse28::Symlink; | |
715 | + | |
716 | +use base qw(test::fuse28::Entity); | |
717 | +use Scalar::Util qw(weaken); | |
718 | + | |
719 | +sub new { | |
720 | + my $class = shift; | |
721 | + my ($existing) = @_; | |
722 | + | |
723 | + my $self = $class->SUPER::new; | |
724 | + $self->{link} = $existing; | |
725 | + | |
726 | + bless $self, $class; | |
727 | +} | |
728 | + | |
729 | +sub readlink { | |
730 | + my $self = shift; | |
731 | + | |
732 | + return $self->{link}; | |
733 | +} | |
734 | + | |
735 | +1; |
@@ -0,0 +1,29 @@ | ||
1 | +#!/usr/local/bin/perl | |
2 | + | |
3 | +# | |
4 | +# You can mount 'fuse28.pm' filesystem by running this script. | |
5 | +# | |
6 | + | |
7 | +use strict; | |
8 | +use lib '..'; | |
9 | +use lib '../blib/lib'; | |
10 | +use lib '../blib/arch/auto'; | |
11 | + | |
12 | +use Cwd qw(abs_path); | |
13 | + | |
14 | +use Fuse; | |
15 | +use test::fuse28; | |
16 | + | |
17 | +my $mount_point = abs_path('mnt'); | |
18 | + | |
19 | +# | |
20 | +# make mount point if not exists | |
21 | +# | |
22 | + | |
23 | +-d $mount_point || mkdir $mount_point, 0777; | |
24 | + | |
25 | +print "fuse version: ", Fuse::fuse_version, "\n"; | |
26 | +print "To umount, run:\n fusermount -u $mount_point\nfrom other terminal.\n"; | |
27 | +my $fs = new test::fuse28; | |
28 | +$fs->main(mountpoint => $mount_point, | |
29 | + debug=>1); |
@@ -0,0 +1,233 @@ | ||
1 | +# | |
2 | +# test filesystem using test::fuse28.pm test mudule | |
3 | +# | |
4 | + | |
5 | +use strict; | |
6 | +use POSIX ":sys_wait_h"; | |
7 | +use Test::Class; | |
8 | +use Test::More; | |
9 | +use Fuse; | |
10 | + | |
11 | +use test::fuse28; | |
12 | + | |
13 | +my $mount_point = "/tmp/fuse-class-test-$<-$$"; | |
14 | + | |
15 | +my $reason = "no reason (test error?)"; | |
16 | + | |
17 | +sub start_check { | |
18 | + # | |
19 | + # Fuse version must be 2.8 or later for this test. | |
20 | + # | |
21 | + my $version = Fuse::fuse_version; | |
22 | + if ($version < 2.6) { | |
23 | + $reason = "Fuse version is lower than 2.6."; | |
24 | + return 0; | |
25 | + } | |
26 | + | |
27 | + # | |
28 | + # fusermount command is available? | |
29 | + # | |
30 | + my $res = `fusermount -V 2>&1`; | |
31 | + unless ($res =~ /version/) { | |
32 | + $reason = "fusermount command is not available."; | |
33 | + return 0; | |
34 | + } | |
35 | + | |
36 | + # | |
37 | + # Test::Virtual::Filesystem | |
38 | + # | |
39 | + eval "use Test::Virtual::Filesystem;"; | |
40 | + if ($@) { | |
41 | + $reason = "Test::Virtual::Filesystem is not available."; | |
42 | + return 0; | |
43 | + } | |
44 | + | |
45 | + return 1; | |
46 | +} | |
47 | + | |
48 | +# | |
49 | +# start Fuse main loop in child process | |
50 | +# | |
51 | +sub child_process { | |
52 | + mkdir $mount_point, 0777; | |
53 | + diag $mount_point; | |
54 | + die "$mount_point: cannot create directoy: $!" unless (-d $mount_point); | |
55 | + | |
56 | + my $fs = new test::fuse28; | |
57 | + $fs->main(mountpoint => $mount_point); | |
58 | +} | |
59 | + | |
60 | +sub cleanup { | |
61 | + my $child_pid = shift; | |
62 | + | |
63 | + my $kid = 0; | |
64 | + my $n = 0; | |
65 | + do { | |
66 | + system("fusermount", "-u", $mount_point); | |
67 | + sleep(1); | |
68 | + $kid = waitpid $child_pid, WNOHANG; | |
69 | + } while($kid != $child_pid && $n++ < 10); | |
70 | + | |
71 | + rmdir $mount_point; | |
72 | +} | |
73 | + | |
74 | +# | |
75 | +# test start | |
76 | +# | |
77 | +unless (start_check) { | |
78 | + plan skip_all => $reason; | |
79 | +} | |
80 | +else { | |
81 | + eval "use Test::Virtual::Filesystem;"; | |
82 | + my $child_pid = -1; | |
83 | + | |
84 | + eval { | |
85 | + plan tests => 165; | |
86 | + | |
87 | + $child_pid = fork(); | |
88 | + die $! if ($child_pid < 0); | |
89 | + | |
90 | + if ($child_pid == 0) { | |
91 | + child_process; | |
92 | + exit 0; | |
93 | + } | |
94 | + | |
95 | + sleep(3); | |
96 | + | |
97 | + my $test = Test::Virtual::Filesystem->new({mountdir => $mount_point, | |
98 | + compatible => '0.08'}); | |
99 | + $test->enable_test_xattr(0); | |
100 | + $test->enable_test_time(1); | |
101 | + # $test->enable_test_atime(1); | |
102 | + # $test->enable_test_mtime(1); | |
103 | + # $test->enable_test_ctime(1); | |
104 | + $test->enable_test_permissions(0); | |
105 | + $test->enable_test_special(0); | |
106 | + # $test->enable_test_fifo(0); | |
107 | + $test->enable_test_symlink(1); | |
108 | + # $test->enable_test_hardlink(0); | |
109 | + # $test->enable_test_nlink(0); | |
110 | + # $test->enable_test_chown(0); | |
111 | + | |
112 | + $test->runtests; | |
113 | + | |
114 | + # unlink | |
115 | + { | |
116 | + my $fname = "$mount_point/unlink-test"; | |
117 | + unlink $fname; | |
118 | + open(my $fh, "> $fname"); | |
119 | + close($fh); | |
120 | + | |
121 | + ok(-f $fname); | |
122 | + unlink $fname; | |
123 | + ok(!-f $fname); | |
124 | + } | |
125 | + | |
126 | + # chmod | |
127 | + { | |
128 | + my $fname = "$mount_point/chmod-test"; | |
129 | + unlink $fname; | |
130 | + open(my $fh, "> $fname"); | |
131 | + close($fh); | |
132 | + chmod(0642, $fname); | |
133 | + my $perm = (stat $fname)[2] & 0777; | |
134 | + ok($perm == 0642); | |
135 | + } | |
136 | + | |
137 | + # ftrunate | |
138 | + { | |
139 | + my $fname = "$mount_point/ftruncate-test"; | |
140 | + unlink $fname; | |
141 | + open(my $fh1, "> $fname"); | |
142 | + print $fh1 "12345"; | |
143 | + close($fh1); | |
144 | + | |
145 | + open(my $fh2, "+< $fname"); | |
146 | + truncate($fh2, 3); | |
147 | + close($fh2); | |
148 | + | |
149 | + my $size = (stat $fname)[7]; | |
150 | + ok($size == 3); | |
151 | + } | |
152 | + | |
153 | + # readdir (1) | |
154 | + { | |
155 | + my $test_dir = "$mount_point/test"; | |
156 | + my $test_dir_1 = "$test_dir/readdir-type-1"; | |
157 | + mkdir $test_dir, 0777; | |
158 | + mkdir $test_dir_1, 0777; | |
159 | + | |
160 | + opendir(my $dh, $test_dir_1); | |
161 | + | |
162 | + my @entries; | |
163 | + while(readdir $dh) { | |
164 | + push(@entries, $_); | |
165 | + } | |
166 | + is(scalar @entries, 2); | |
167 | + is(scalar(grep { $_ eq '..' } @entries), 1); | |
168 | + is(scalar(grep { $_ eq '.' } @entries), 1); | |
169 | + } | |
170 | + | |
171 | + # readdir (2) | |
172 | + { | |
173 | + my $test_dir = "$mount_point/test"; | |
174 | + my $test_dir_2 = "$test_dir/readdir-type-2"; | |
175 | + mkdir $test_dir, 0777; | |
176 | + mkdir $test_dir_2, 0777; | |
177 | + | |
178 | + opendir(my $dh, $test_dir_2); | |
179 | + | |
180 | + my @entries; | |
181 | + while(readdir $dh) { | |
182 | + push(@entries, $_); | |
183 | + } | |
184 | + | |
185 | + is(scalar @entries, 2); | |
186 | + is(scalar(grep { $_ eq '..' } @entries), 1); | |
187 | + is(scalar(grep { $_ eq '.' } @entries), 1); | |
188 | + } | |
189 | + | |
190 | + # access | |
191 | + { | |
192 | + use POSIX; | |
193 | + | |
194 | + my $test_dir = "$mount_point/test"; | |
195 | + mkdir $test_dir, 0777; | |
196 | + | |
197 | + my $test_dir_bad = "$test_dir/access_no_perm"; | |
198 | + mkdir $test_dir_bad, 0777; | |
199 | + my $test_dir_ok = "$test_dir/access_with_perm"; | |
200 | + mkdir $test_dir_ok, 0777; | |
201 | + | |
202 | + ok(!POSIX::access($test_dir_bad, &POSIX::R_OK)); | |
203 | + ok(POSIX::access($test_dir_ok, &POSIX::R_OK)); | |
204 | + } | |
205 | + | |
206 | + # ftruncate & fgetattr | |
207 | + { | |
208 | + my $test_dir = "$mount_point/test"; | |
209 | + mkdir $test_dir, 0777; | |
210 | + | |
211 | + my $file = "$test_dir/ftruncate_test"; | |
212 | + open(my $fh1, ">", $file); | |
213 | + print $fh1 "hello world\n"; | |
214 | + close($fh1); | |
215 | + | |
216 | + open(my $fh2, "+<", $file); | |
217 | + truncate($fh2, 3); | |
218 | + | |
219 | + my @st = stat($fh2); | |
220 | + is($st[7], 3); | |
221 | + | |
222 | + close($fh2); | |
223 | + | |
224 | + @st = stat($file); | |
225 | + is($st[7], 3); | |
226 | + } | |
227 | + }; | |
228 | + | |
229 | + my $err = $@; | |
230 | + cleanup($child_pid); | |
231 | + | |
232 | + die $err if ($err); | |
233 | +} |
@@ -0,0 +1,89 @@ | ||
1 | + | |
2 | +use strict; | |
3 | + | |
4 | +use Test::More tests => 14; | |
5 | + | |
6 | +################################################# | |
7 | +package TestClass; | |
8 | + | |
9 | +use base qw(Fuse::Class); | |
10 | + | |
11 | +sub getdir { | |
12 | + my $self = shift; | |
13 | + my ($dir) = @_; | |
14 | + | |
15 | + return (".", "..", "x$dir", 0); | |
16 | +} | |
17 | + | |
18 | +sub mknod { | |
19 | + my $self = shift; | |
20 | + my ($fname) = @_; | |
21 | + | |
22 | + $! = Errno::EINTR; | |
23 | + die "error"; | |
24 | +} | |
25 | + | |
26 | +sub mkdir { | |
27 | + my $self = shift; | |
28 | + my ($fname) = @_; | |
29 | + | |
30 | + $! = 0; | |
31 | + die "error"; | |
32 | +} | |
33 | + | |
34 | +################################################# | |
35 | + | |
36 | +package main; | |
37 | + | |
38 | +my %Fuse_main; | |
39 | + | |
40 | +# | |
41 | +# override Fuse::main for test | |
42 | +# | |
43 | +{ | |
44 | + no warnings "redefine"; | |
45 | + no strict "refs"; | |
46 | + | |
47 | + *Fuse::main = sub { | |
48 | + %Fuse_main = @_; | |
49 | + for my $k (keys %Fuse_main) { | |
50 | + my $subname = $Fuse_main{$k}; | |
51 | + $Fuse_main{$k} = sub { &$subname(@_); }; | |
52 | + } | |
53 | + }; | |
54 | +} | |
55 | + | |
56 | +use Errno; | |
57 | + | |
58 | +my $fs = TestClass->new(); | |
59 | +is(ref($fs), "TestClass", "constructor"); | |
60 | + | |
61 | +$fs->main(mountpoint => "/never-found"); | |
62 | +$Fuse::Class::_Module = $fs; # set in Fuse::Class | |
63 | + | |
64 | +# overrided method | |
65 | +my $getdir = $Fuse_main{getdir}; | |
66 | +is_deeply([&$getdir("abc")], [".", "..", "xabc", 0]); | |
67 | + | |
68 | +# error (in $!) | |
69 | +my $mknod = $Fuse_main{mknod}; | |
70 | +is(&$mknod("/never-found"), -Errno::EINTR()); | |
71 | + | |
72 | +# error (not in $!) | |
73 | +my $mkdir = $Fuse_main{mkdir}; | |
74 | +is(&$mkdir("/never-found"), -Errno::EPERM()); | |
75 | + | |
76 | +# not implemented | |
77 | +my $getattr = $Fuse_main{getattr}; | |
78 | +is($fs->can('getattr') ? &$getattr("/") : -Errno::EPERM(), -Errno::EPERM()); | |
79 | + | |
80 | +# default implement | |
81 | +is($fs->readlink("/not/found"), -Errno::ENOENT()); | |
82 | +is($fs->statfs(), -Errno::ENOANO()); | |
83 | +is($fs->flush("/some/file"), 0); | |
84 | +is($fs->release("/some/file"), 0); | |
85 | +is($fs->fsync("/some/file"), 0); | |
86 | +is($fs->getxattr("/some/file"), 0); | |
87 | +is($fs->listxattr("/some/file"), 0); | |
88 | +is($fs->removexattr("/some/file"), 0); | |
89 | +is($fs->setxattr("/some/file", "name", "value", 0), -Errno::EOPNOTSUPP()); |
@@ -0,0 +1,12 @@ | ||
1 | +#!perl -T | |
2 | + | |
3 | +use strict; | |
4 | +use warnings; | |
5 | +use Test::More; | |
6 | + | |
7 | +# Ensure a recent version of Test::Pod | |
8 | +my $min_tp = 1.22; | |
9 | +eval "use Test::Pod $min_tp"; | |
10 | +plan skip_all => "Test::Pod $min_tp required for testing POD" if $@; | |
11 | + | |
12 | +all_pod_files_ok(); |
@@ -0,0 +1,18 @@ | ||
1 | +use strict; | |
2 | +use warnings; | |
3 | +use Test::More; | |
4 | + | |
5 | +# Ensure a recent version of Test::Pod::Coverage | |
6 | +my $min_tpc = 1.08; | |
7 | +eval "use Test::Pod::Coverage $min_tpc"; | |
8 | +plan skip_all => "Test::Pod::Coverage $min_tpc required for testing POD coverage" | |
9 | + if $@; | |
10 | + | |
11 | +# Test::Pod::Coverage doesn't require a minimum Pod::Coverage version, | |
12 | +# but older versions don't recognize some common documentation styles | |
13 | +my $min_pc = 0.18; | |
14 | +eval "use Pod::Coverage $min_pc"; | |
15 | +plan skip_all => "Pod::Coverage $min_pc required for testing POD coverage" | |
16 | + if $@; | |
17 | + | |
18 | +all_pod_coverage_ok(); |
@@ -0,0 +1,9 @@ | ||
1 | +#!perl -T | |
2 | + | |
3 | +use Test::More tests => 1; | |
4 | + | |
5 | +BEGIN { | |
6 | + use_ok( 'Fuse::Class' ); | |
7 | +} | |
8 | + | |
9 | +diag( "Testing Fuse::Class $Fuse::Class::VERSION, Perl $], $^X" ); |
@@ -0,0 +1,649 @@ | ||
1 | +# | |
2 | +# Fuse::Class | |
3 | +# | |
4 | +# For implementation using class. | |
5 | +# | |
6 | + | |
7 | +package Fuse::Class; | |
8 | + | |
9 | +use warnings; | |
10 | +use strict; | |
11 | + | |
12 | +=head1 NAME | |
13 | + | |
14 | +Fuse::Class - Base clsas for Fuse module implementation using class. | |
15 | + | |
16 | +=head1 VERSION | |
17 | + | |
18 | +Version 0.02 | |
19 | + | |
20 | +=cut | |
21 | + | |
22 | +our $VERSION = '0.02'; | |
23 | + | |
24 | +=head1 SYNOPSIS | |
25 | + | |
26 | +Fuse::Class is just a abstract class. First, you must write subclass | |
27 | +overriding methods like named 'getattr'. (callbacks defined in Fuse) | |
28 | + | |
29 | +Subclass will be written like following: | |
30 | + | |
31 | + package SampleFS; | |
32 | + | |
33 | + use base qw(Fuse::Class); | |
34 | + | |
35 | + sub getattr { | |
36 | + my $self = shift; # instance or class is passed as first argment. | |
37 | + my ($fname) = @_; # same as Fuse. | |
38 | + | |
39 | + ... | |
40 | + | |
41 | + return @attr; # same as Fuse. | |
42 | + } | |
43 | + ... | |
44 | + | |
45 | +To mount your filesystem: | |
46 | + | |
47 | + use SampleFS; | |
48 | + | |
49 | + my $fuse = SampleFS->new("your", "parameters", "here"); | |
50 | + $fuse->main(mountpoint => '/mnt/sample', mountopts => "allow_other"); | |
51 | + | |
52 | + # control will be not returned until file system is unmouted... | |
53 | + | |
54 | +When file on your filesystem is opened, it will be seen that method | |
55 | +is called like this: | |
56 | + | |
57 | + $fuse->open($path_name, $flags, $file_info); | |
58 | + | |
59 | +=head1 DESCRIPTION | |
60 | + | |
61 | +This module supports writing Fuse callback as method. | |
62 | +Method name is same as Fuse callback, but first argment is an object (it's named '$self' usually). | |
63 | + | |
64 | +This is a small change for Fuse, but you can use power of OO like | |
65 | +inheritance, encapsulation, ... | |
66 | + | |
67 | +Exception handling: | |
68 | + | |
69 | +Returned value will be treated as negative errno in Fuse way, but you can | |
70 | +use exception, too. | |
71 | +If exception is thrown in your method ("die" is called), $! will be used | |
72 | +as errno to notify error to Fuse. | |
73 | + | |
74 | + | |
75 | +=head1 EXPORT | |
76 | + | |
77 | +Nothing. | |
78 | + | |
79 | +=head1 CONSTRUCTOR | |
80 | + | |
81 | +=cut | |
82 | + | |
83 | +use Fuse; | |
84 | +use Errno; | |
85 | + | |
86 | +# instance calling main | |
87 | +use vars qw($_Module); | |
88 | + | |
89 | +=head2 new | |
90 | + | |
91 | +Create a new instance. This method is defined just for your convenience. | |
92 | +Default implementation returns blessed empty HASHREF. | |
93 | + | |
94 | +=cut | |
95 | + | |
96 | +# | |
97 | +# for your convenience. | |
98 | +# | |
99 | +sub new { | |
100 | + my $class = shift; | |
101 | + bless {}, $class; | |
102 | +} | |
103 | + | |
104 | +my @callback; | |
105 | + | |
106 | +=head1 METHODS | |
107 | + | |
108 | +=cut | |
109 | + | |
110 | +=head2 main(OPT_KEY1 => OPT_VALUE1, OPT_KEY2 => OPT_VALUE2, ...) | |
111 | + | |
112 | +Start a main loop. Filesystem is mounted to the mountpoint pointed by | |
113 | +option "mountpoint". | |
114 | + | |
115 | +Options are taken as key=>value pair selected from following: | |
116 | + | |
117 | +=over | |
118 | + | |
119 | +=item debug => boolean | |
120 | + | |
121 | +This option controls tracing on or off. (Default is off). | |
122 | + | |
123 | +=item mountpoint => "path_to_mountpoint" | |
124 | + | |
125 | +Directory name to mount filesystem like "/mnt/mypoint". | |
126 | +This option has no default value and is mandatory. | |
127 | + | |
128 | +=item mountopts => "opt1,op2,..." | |
129 | + | |
130 | +Comma separated options for FUSE kernel module. | |
131 | + | |
132 | +=item nullpath_ok => boolean | |
133 | + | |
134 | +If true, empty pathname is passed to the methods like read, write, flush, | |
135 | +release, fsync, readdir, releasedir, fsyncdir, ftruncate, fgetattr and lock. | |
136 | + | |
137 | +To use this option, you must return file/directory handle from | |
138 | +open, opendir and create, and you must operate file/directory by | |
139 | +that handle insted of pathname. | |
140 | + | |
141 | +Only effective on Fuse 2.8 or later. | |
142 | + | |
143 | +=back | |
144 | + | |
145 | +For more information, see the documentation of Fuse. | |
146 | + | |
147 | +=cut | |
148 | + | |
149 | +sub main { | |
150 | + my $self = shift; | |
151 | + my %attr = @_; | |
152 | + | |
153 | + my @args; | |
154 | + for my $opt (qw(debug mountpoint mountopts nullpath_ok)) { | |
155 | + push(@args, $opt, $attr{$opt}) if (defined($attr{$opt})); | |
156 | + } | |
157 | + | |
158 | + local $_Module = $self; | |
159 | + | |
160 | + my %fnmap; | |
161 | + foreach my $fnname (@callback) { | |
162 | + if ($_Module->can($fnname)) { | |
163 | + $fnmap{$fnname} = __PACKAGE__ . '::_' . $fnname; | |
164 | + } | |
165 | + } | |
166 | + | |
167 | + Fuse::main(@args, %fnmap); | |
168 | +} | |
169 | + | |
170 | +BEGIN { | |
171 | + @callback = qw (getattr readlink getdir mknod mkdir unlink | |
172 | + rmdir symlink rename link chmod chown truncate | |
173 | + utime open read write statfs flush release fsync | |
174 | + setxattr getxattr listxattr removexattr); | |
175 | + if (Fuse->can('fuse_version')) { | |
176 | + my $fuse_version = Fuse::fuse_version(); | |
177 | + if ($fuse_version >= 2.3) { | |
178 | + push(@callback, qw(opendir readdir releasedir fsyncdir init destroy)); | |
179 | + } | |
180 | + if ($fuse_version >= 2.5) { | |
181 | + push(@callback, qw(access create ftruncate fgetattr)); | |
182 | + } | |
183 | + if ($fuse_version >= 2.6) { | |
184 | + push(@callback, qw(lock utimens bmap)); | |
185 | + } | |
186 | + } | |
187 | + | |
188 | + no strict "refs"; | |
189 | + for my $m (@callback) { | |
190 | + my $method = __PACKAGE__ . "::_$m"; | |
191 | + | |
192 | + *$method = sub { | |
193 | + my $method_name = $m; | |
194 | + | |
195 | + if ($_Module->can($method_name)) { | |
196 | + my @ret = eval { | |
197 | + $_Module->$m(@_); | |
198 | + }; | |
199 | + if ($@) { | |
200 | + return $! ? -$! : -Errno::EPERM(); | |
201 | + } | |
202 | + else { | |
203 | + return (wantarray() ? @ret : $ret[0]); | |
204 | + } | |
205 | + } | |
206 | + else { | |
207 | + return -Errno::EPERM(); | |
208 | + } | |
209 | + } | |
210 | + } | |
211 | +} | |
212 | + | |
213 | +=head1 METHODS MAY BE OVERRIDDEN | |
214 | + | |
215 | +=cut | |
216 | + | |
217 | +=head2 getattr(PATH_NAME) | |
218 | + | |
219 | +Return a list of file attributes. Meaning of fields are same as | |
220 | +"stat" function like this: | |
221 | + | |
222 | + ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size, | |
223 | + $atime,$mtime,$ctime,$blksize,$blocks) | |
224 | + | |
225 | +On error, return scalar value like -ENOENT(). | |
226 | + | |
227 | +=head2 readlink(PATH_NAME) | |
228 | + | |
229 | +This method is called to dereference symbolic link. | |
230 | +Return a destination path string or numeric error value. | |
231 | + | |
232 | +By Default implementation, returns -ENOENT(). | |
233 | +You can leave this method if your FS does not have symlink. | |
234 | + | |
235 | +=cut | |
236 | + | |
237 | +sub readlink { | |
238 | + return -Errno::ENOENT(); | |
239 | +} | |
240 | + | |
241 | +=head2 getdir(DIRECTORY_NAME) | |
242 | + | |
243 | +Return a list of file/directory names and an errno (0 if success). | |
244 | +ex: ('..', '.', 'a', 'b', 0) | |
245 | + | |
246 | +If 'readdir' method is implemented, this function will never be called. | |
247 | + | |
248 | +=head2 mknod(PATH_NAME, MODE, DEVNO) | |
249 | + | |
250 | +Return an errno (0 if success). | |
251 | +This method is called to create an entity (device or file). | |
252 | + | |
253 | +=head2 mkdir(DIRECTORY_NAME, MODE) | |
254 | + | |
255 | +Return an errno (0 if success). | |
256 | +This method is called to create a directory. | |
257 | + | |
258 | +=head2 unlink(PATH_NAME) | |
259 | + | |
260 | +Return an errno (0 if success). | |
261 | +This method is called to remove an entity (device, file or symlink). | |
262 | + | |
263 | +=head2 rmdir(PATH_NAME) | |
264 | + | |
265 | +Return an errno (0 if success). | |
266 | +This method is called to remove a directory. | |
267 | + | |
268 | +=head2 symlink(EXISTING_PATH_NAME, SYMLINK_NAME) | |
269 | + | |
270 | +Return an errno (0 if success). | |
271 | +This method is called to create a symbolic link. | |
272 | + | |
273 | +=head2 rename(OLD_NAME, NEW_NAME) | |
274 | + | |
275 | +Return an errno (0 if success). | |
276 | +This method is called to rename/move a entity. | |
277 | + | |
278 | +=head2 link(EXISTING_PATH_NAME, HADLINK_NAME) | |
279 | + | |
280 | +Return an errno (0 if success). | |
281 | +This method is called to create a hard link. | |
282 | + | |
283 | +=head2 chmod(PATH_NAME, MODE). | |
284 | + | |
285 | +Return an errno (0 if success). | |
286 | +This method is called to change permissions on a entity. | |
287 | + | |
288 | +=head2 chown(PATH_NAME, UID, GID). | |
289 | + | |
290 | +Return an errno (0 if success). | |
291 | +This method is called to change ownership of a entity. | |
292 | + | |
293 | +=head2 truncate(PATH_NAME, OFFSET). | |
294 | + | |
295 | +Return an errno (0 if success). | |
296 | +This method is called to truncate a file at the given offset. | |
297 | + | |
298 | +=head2 utime(PATH_NAME, ACCESS_TIME, MODIFIED_TIME). | |
299 | + | |
300 | +Return an errno (0 if success). | |
301 | +This method is called to change atime/mtime on a entity. | |
302 | + | |
303 | +=head2 open(PATH_NAME, FLAGS, FILE_INFO) | |
304 | + | |
305 | +Return an errno, and a file handle (optional) | |
306 | + | |
307 | +First style means like this: | |
308 | + | |
309 | + return 0; # success | |
310 | + | |
311 | +and second one is following: | |
312 | + | |
313 | + return (0, $file_handle_you_made); # success and handle | |
314 | + | |
315 | +FLAGS is an OR-combined value of flags (O_RDONLY, O_SYNC, etc). | |
316 | +FILE_INFO is a hashref. | |
317 | + | |
318 | +Returned file handle will be passed to subsequent method call | |
319 | +to operate on opend file. | |
320 | + | |
321 | +=head2 read(PATH_NAME, SIZE, OFFSET, FILE_HANDLE) | |
322 | + | |
323 | +Return an errno, or string scalar of read data. | |
324 | + | |
325 | +This method is called to read data (SIZE bytes) | |
326 | +at the given offset of opened file. | |
327 | + | |
328 | +=head2 write(PATH_NAME, BUFFER, OFFSET, FILE_HANDLE) | |
329 | + | |
330 | +Return a written byte size or an errno. | |
331 | + | |
332 | +This method is called to write data (BUFFER) | |
333 | +at the given offset of opened file. | |
334 | + | |
335 | +=head2 statfs | |
336 | + | |
337 | +Return status of filesystem in one of follwing style: | |
338 | + | |
339 | +=over | |
340 | + | |
341 | +=item -ENOANO() | |
342 | + | |
343 | +or | |
344 | + | |
345 | +=item $namelen, $files, $files_free, $blocks, $blocks_avail, $blocksize | |
346 | + | |
347 | +or | |
348 | + | |
349 | +=item -ENOANO(), $namelen, $files, $files_free, $blocks, $blocks_avail, $blocksize | |
350 | + | |
351 | +=back | |
352 | + | |
353 | +=cut | |
354 | + | |
355 | +sub statfs { | |
356 | + return -Errno::ENOANO(); | |
357 | +} | |
358 | + | |
359 | +=head2 flush(PATH_NAME, FILE_HANDLE) | |
360 | + | |
361 | +Return an errno (0 if success). | |
362 | +This method is called to synchronize any cached data. | |
363 | + | |
364 | +=cut | |
365 | + | |
366 | +sub flush { | |
367 | + return 0; | |
368 | +} | |
369 | + | |
370 | +=head2 release(PATH_NAME, FLAGS, FILE_HANDLE) | |
371 | + | |
372 | +Return an errno (0 if success). | |
373 | + | |
374 | +FLAGS is a same value passed when 'open' is called. | |
375 | + | |
376 | +Called to indicate that there are no more references to the file and flags. | |
377 | + | |
378 | +=cut | |
379 | + | |
380 | +sub release { | |
381 | + return 0; | |
382 | +} | |
383 | + | |
384 | +=head2 fsync(PATH_NAME, DATA_SYNC, FILE_HANDLE) | |
385 | + | |
386 | +Return an errno (0 if success). | |
387 | + | |
388 | +Called to synchronize file contents. | |
389 | + | |
390 | +DATA_SYNC indicates 'user data only'. If DATA_SYNC is non-zero, | |
391 | +only the user data should be synchronized. Otherwise synchronize | |
392 | +user and meta data. | |
393 | + | |
394 | +=cut | |
395 | + | |
396 | +sub fsync { | |
397 | + return 0; | |
398 | +} | |
399 | + | |
400 | +=head2 setxattr(PATH_NAME, ATTR_NAME, ATTR_VALUE, FLAGS) | |
401 | + | |
402 | +FLAGS is OR-ed value of Fuse::XATTR_CREATE and Fuse::XATTR_REPLACE | |
403 | + | |
404 | +Return an errno (0 if success). | |
405 | + | |
406 | +This method is called to set extended attribute. | |
407 | + | |
408 | +-EOPNOTSUPP means that setting the attribute is rejected. | |
409 | + | |
410 | +If XATTR_CREATE is passed and the attribute already exists, return -EEXIST. | |
411 | + | |
412 | +If XATTR_REPLACE is passed and the attribute does not exist, return -ENOATTR. | |
413 | + | |
414 | +By default implementation, returns -EOPNOTSUPP. | |
415 | +You can leave this method if your FS does not have any extended attributes. | |
416 | + | |
417 | +=cut | |
418 | + | |
419 | +sub setxattr { | |
420 | + return -Errno::EOPNOTSUPP(); | |
421 | +} | |
422 | + | |
423 | +=head2 getxattr(PATH_NAME, ATTR_NAME) | |
424 | + | |
425 | +Return attribute value or errno (0 if no value). | |
426 | + | |
427 | +This method is called to get extended attribute value. | |
428 | + | |
429 | +By default implementation, returns 0. | |
430 | +You can leave this method if your FS does not have any extended attributes. | |
431 | + | |
432 | +=cut | |
433 | + | |
434 | +sub getxattr { | |
435 | + return 0; | |
436 | +} | |
437 | + | |
438 | +=head2 listxattr(PATH_NAME) | |
439 | + | |
440 | +Return a list of attribute names and an errno (0 if success). | |
441 | +ex: ('attr1', 'attr2', 'attr3', 0) | |
442 | + | |
443 | +By default implementation, returns 0. | |
444 | +You can leave this method if your FS does not have any extended attributes. | |
445 | + | |
446 | +=cut | |
447 | + | |
448 | +sub listxattr { | |
449 | + return 0; | |
450 | +} | |
451 | + | |
452 | +=head2 removexattr(PATH_NAME, ATTR_NAME) | |
453 | + | |
454 | +Return an errno (0 if success). | |
455 | + | |
456 | +This method is called to remove an attribute from entity. | |
457 | + | |
458 | +By default implementation, returns 0. | |
459 | +You can leave this method if your FS does not have any extended attributes. | |
460 | + | |
461 | +=cut | |
462 | + | |
463 | +sub removexattr { | |
464 | + return 0; | |
465 | +} | |
466 | + | |
467 | +=head2 opendir(DIRECTORY_NAME) | |
468 | + | |
469 | +Return an errno, and a directory handle (optional). | |
470 | + | |
471 | +This method is called to open a directory for reading. | |
472 | +If special handling is required to open a directory, this method | |
473 | +can be implemented. | |
474 | + | |
475 | +Supported by Fuse version 2.3 or later. | |
476 | + | |
477 | +=cut | |
478 | + | |
479 | +# sub opendir { | |
480 | +# return -Errno::EOPNOTSUPP(); | |
481 | +# } | |
482 | + | |
483 | +=head2 readdir(DIRECTORY_NAME, OFFSET, HANDLE) | |
484 | + | |
485 | +(HANDLE is optional. see opendir) | |
486 | + | |
487 | +Return list consists of entries and an errno. Most simple style is | |
488 | +same as getdir(). ex: ('..', '.', 'a', 'b', 0) | |
489 | + | |
490 | +Entry can be array ref containing offset and attributes in following way: | |
491 | + | |
492 | + ([1, '..'], [2, '.'], [3, 'a'], [4, 'b', ], 0) | |
493 | + | |
494 | +or | |
495 | + ([1, '..', [array_like_getattr]], [2, '.', [array_like_getattr]], 0) | |
496 | + | |
497 | + | |
498 | +Supported by Fuse version 2.3 or later. | |
499 | + | |
500 | +=cut | |
501 | + | |
502 | +# sub readdir { | |
503 | +# return -Errno::EOPNOTSUPP(); | |
504 | +# } | |
505 | + | |
506 | +=head2 releasedir(DIRECTORY_NAME, HANDLE) | |
507 | + | |
508 | +(HANDLE is optional. see opendir) | |
509 | + | |
510 | +Return an errno (0 if success). | |
511 | + | |
512 | +Called to indicate that there are no more references to the opened directory. | |
513 | + | |
514 | +Supported by Fuse version 2.3 or later. | |
515 | + | |
516 | +=cut | |
517 | + | |
518 | +=head2 fsyncdir(DIRECTORY_NAME, FLAGS, HANDLE) | |
519 | + | |
520 | +(HANDLE is optional. see opendir) | |
521 | + | |
522 | +Return an errno (0 if success). | |
523 | +This method is called to synchronize user data (FLAG is non-zero value) | |
524 | +or user data and meta data in directory. | |
525 | + | |
526 | +Supported by Fuse version 2.3 or later. | |
527 | + | |
528 | +=cut | |
529 | + | |
530 | +=head2 init | |
531 | + | |
532 | +You can return scalar. It can be accessed using fuse_get_context(). | |
533 | + | |
534 | +Supported by Fuse version 2.3 or later. | |
535 | + | |
536 | +=cut | |
537 | + | |
538 | +=head2 destroy(SCALAR_VALUE) | |
539 | + | |
540 | +(SCALAR_VALUE is returned value by init method) | |
541 | + | |
542 | +Supported by Fuse version 2.3 or later. | |
543 | + | |
544 | +=cut | |
545 | + | |
546 | +=head2 access(PATH_NAME, ACCESS_MODE_FLAG) | |
547 | + | |
548 | +Return an errno (0 if success). | |
549 | + | |
550 | +This method is called to determine if user can access the file. | |
551 | +For more information, see Fuse document and manual for access(2). | |
552 | + | |
553 | +Supported by Fuse version 2.5 or later. | |
554 | + | |
555 | +=cut | |
556 | + | |
557 | +=head2 create(PATH_NAME, MASK, MODE) | |
558 | + | |
559 | +Return an errno, and a file handle (optional) | |
560 | + | |
561 | +This method is called to create a file with MASK (like mknod) | |
562 | +and open it with MODE atomically. | |
563 | +If this method is not implemented, mknod() and open() will be used. | |
564 | + | |
565 | +Supported by Fuse version 2.5 or later. | |
566 | + | |
567 | +=cut | |
568 | + | |
569 | +=head2 ftruncate(PATH_NAME, OFFSET, FILE_HANDLE) | |
570 | + | |
571 | +(HANDLE is optional. see open) | |
572 | + | |
573 | +Return an errno (0 if success). | |
574 | +This method is called to truncate an opened file at the given offset. | |
575 | + | |
576 | +Supported by Fuse version 2.5 or later. | |
577 | + | |
578 | +=cut | |
579 | + | |
580 | +=head2 fgetattr(PATH_NAME, FILE_HANDLE) | |
581 | + | |
582 | +(HANDLE is optional. see open) | |
583 | + | |
584 | +Return a list of file attributes like getattr(). | |
585 | +This method is called to get attributes for opened file. | |
586 | + | |
587 | +Supported by Fuse version 2.5 or later. | |
588 | + | |
589 | +=cut | |
590 | + | |
591 | +=head2 lock(PATH_NAME, COMMAND_CODE, LOCK_PARAMS, FILE_HANDLE) | |
592 | + | |
593 | +(HANDLE is optional. see open) | |
594 | + | |
595 | +Return an errno (0 if success). | |
596 | + | |
597 | +This method is called to lock or unlock regions of file. Parameters | |
598 | +for locking is passed in LOCK_PARAMS as hashref. | |
599 | + | |
600 | +For more information, see the documentation of Fuse. | |
601 | + | |
602 | +Supported by Fuse version 2.6 or later. | |
603 | + | |
604 | +=cut | |
605 | + | |
606 | +=head2 utimens(PATH_NAME, ACCESS_TIME, MODIFIED_TIME) | |
607 | + | |
608 | +Return an errno (0 if success). | |
609 | +This method is called to change atime/mtime on a entity. | |
610 | +(Time has a resolution in nanosecond.) | |
611 | + | |
612 | +Supported by Fuse version 2.6 or later. | |
613 | + | |
614 | +=cut | |
615 | + | |
616 | +=head2 bmap(PATH_NAME, BLOCK_SIZE, BLOCK_NUMBER) | |
617 | + | |
618 | +Return 0 and physical block numeber on success, otherwise errno. | |
619 | + | |
620 | +This method is called to get physical block offset on block device. | |
621 | + | |
622 | +For more information, see the documentation of Fuse. | |
623 | + | |
624 | +Supported by Fuse version 2.6 or later. | |
625 | + | |
626 | +=cut | |
627 | + | |
628 | +=head1 AUTHOR | |
629 | + | |
630 | +Toshimitsu FUJIWARA, C<< <tttfjw at gmail.com> >> | |
631 | + | |
632 | +=head1 BUGS | |
633 | + | |
634 | +Threading is not tested. | |
635 | + | |
636 | +=head1 COPYRIGHT & LICENSE | |
637 | + | |
638 | +Copyright 2008-2011 Toshimitsu FUJIWARA, all rights reserved. | |
639 | + | |
640 | +This program is free software; you can redistribute it and/or modify it | |
641 | +under the same terms as Perl itself. | |
642 | + | |
643 | +=head1 SEE ALSO | |
644 | + | |
645 | +Fuse | |
646 | + | |
647 | +=cut | |
648 | + | |
649 | +1; # End of xxx |
@@ -0,0 +1,38 @@ | ||
1 | +Fuse-Class | |
2 | + | |
3 | +Fuse::Class - Base clsas for Fuse module implementation using class. | |
4 | + | |
5 | +This module supports writing Fuse callback as method. | |
6 | +Method name is same as Fuse callback, but first argment is object. | |
7 | + | |
8 | +This is a small change for Fuse, but you can use power of OO like | |
9 | +inheritance, encapsulation, ... | |
10 | + | |
11 | + | |
12 | +INSTALLATION | |
13 | + | |
14 | +To install this module, run the following commands: | |
15 | + | |
16 | + perl Makefile.PL | |
17 | + make | |
18 | + make test | |
19 | + make install | |
20 | + | |
21 | +(For the full test, you need Test::Virtual::Filesystem module.) | |
22 | + | |
23 | + | |
24 | +SUPPORT AND DOCUMENTATION | |
25 | + | |
26 | +After installing, you can find documentation for this module with the | |
27 | +perldoc command. | |
28 | + | |
29 | + perldoc Fuse::Class | |
30 | + | |
31 | + | |
32 | +COPYRIGHT AND LICENCE | |
33 | + | |
34 | +Copyright (C) 2008 Toshimitsu FUJIWARA | |
35 | + | |
36 | +This program is free software; you can redistribute it and/or modify it | |
37 | +under the same terms as Perl itself. | |
38 | + |
@@ -0,0 +1,12 @@ | ||
1 | +# http://module-build.sourceforge.net/META-spec.html | |
2 | +#XXXXXXX This is a prototype!!! It will change in the future!!! XXXXX# | |
3 | +name: Fuse-Class | |
4 | +version: 0.02 | |
5 | +version_from: lib/Fuse/Class.pm | |
6 | +installdirs: site | |
7 | +requires: | |
8 | + Fuse: 0 | |
9 | + Test::More: 0 | |
10 | + | |
11 | +distribution_type: module | |
12 | +generated_by: ExtUtils::MakeMaker version 6.17 |