3 # Loopback fs that shows only files with a particular xattr
5 # Reuben Thomas 29th November 2007, based on example code from Fuse package
11 use File::ExtAttr ':all';
13 use POSIX qw(ENOENT ENOSYS EEXIST EPERM O_RDONLY O_RDWR O_APPEND O_CREAT O_ACCMODE);
14 use Fcntl qw(S_ISBLK S_ISCHR S_ISFIFO SEEK_SET);
20 my ($tag, $real_root, $mountpoint);
24 print STDERR shift if $debug;
27 my $can_syscall = eval {
28 require 'syscall.ph'; # for SYS_mknod and SYS_lchown
31 if (!$can_syscall && open my $fh, '<', '/usr/include/sys/syscall.h') {
32 my %sys = do { local $/ = undef;
33 <$fh> =~ m/\#define \s+ (\w+) \s+ (\d+)/gxms;
36 if ($sys{SYS_mknod} && $sys{SYS_lchown}) {
37 *SYS_mknod = sub { $sys{SYS_mknod} };
38 *SYS_lchown = sub { $sys{SYS_lchown} };
46 my $ret = getfattr($file, $tag);
47 debug("tagged: $file $tag " . defined($ret) . "\n");
52 return setfattr(shift, $tag, "");
56 return delfattr(shift, $tag);
60 return $real_root . shift;
65 return $err ? 0 : -$!;
70 my ($file) = append_root(shift);
71 return -ENOENT() unless tagged($file);
72 my (@list) = lstat($file);
73 return -$! unless @list;
79 return readlink(append_root(shift));
84 my ($dirname) = append_root(shift);
85 return -ENOENT() unless tagged($dirname) && opendir(DIRHANDLE, $dirname);
86 my (@files) = readdir(DIRHANDLE);
88 my @psifiles = grep {tagged("$dirname/$_")} @files;
89 return (@psifiles, 0);
93 my ($file, $modes, $dev) = @_;
94 return -ENOSYS() if !$can_syscall;
96 $file = append_root($file);
97 return -EEXIST() if -e $file && !tagged($file);
99 syscall(&SYS_mknod, $file, $modes, $dev);
100 tag($file) if $! == 0;
106 my ($name, $perm) = @_;
107 $name = append_root($name);
108 return err(mkdir($name, $perm));
112 my ($file) = append_root(shift);
114 my $accmode = $mode & O_ACCMODE;
115 debug("x_open $accmode " . O_ACCMODE . " " . O_WRONLY . " " . O_RDWR . " ");
116 if ($accmode == O_WRONLY || $accmode == O_RDWR) {
117 return -EEXIST() if -e $file && !tagged($file);
119 return -ENOENT() unless tagged($file);
121 return -$! unless sysopen(FILE, $file, $mode);
128 my ($file, $bufsize, $off) = @_;
129 my ($rv) = -ENOSYS();
130 my ($handle) = new IO::File;
131 $file = append_root($file);
132 return -ENOENT() unless tagged($file);
133 my ($fsize) = -s $file;
134 return -ENOSYS() unless open($handle, $file);
135 if(seek($handle, $off, SEEK_SET)) {
136 read($handle, $rv, $bufsize);
143 my ($file, $buf, $off) = @_;
145 $file = append_root($file);
146 return -ENOENT() unless tagged($file);
147 my ($fsize) = -s $file;
148 return -ENOSYS() unless open(FILE, '+<', $file);
149 if ($rv = seek(FILE, $off, SEEK_SET)) {
150 $rv = print(FILE $buf);
152 $rv = -ENOSYS() unless $rv;
159 my ($file) = append_root(shift);
160 return -ENOENT() unless tagged($file);
161 return err(detag($file));
167 my ($new) = append_root(shift);
168 return -EEXIST() if -e $new && !tagged($new);
169 return err(symlink($old, $new));
174 my ($old) = append_root(shift);
175 my ($new) = append_root(shift);
176 return -ENOENT() unless tagged($old);
177 return -EEXIST() unless !-e $new || tagged($new);
178 my ($err) = rename($old, $new) ? 0 : -ENOENT();
184 my ($old) = append_root(shift);
185 my ($new) = append_root(shift);
186 return -ENOENT() unless tagged($old);
187 return -EEXIST() unless !-e $new || tagged($new);
188 return err(link($old, $new));
192 return -ENOSYS() if !$can_syscall;
194 my ($fn) = append_root(shift);
195 return -ENOENT() unless tagged($fn);
196 my ($uid, $gid) = @_;
197 # perl's chown() does not chown symlinks, it chowns the symlink's
198 # target. it fails when the link's target doesn't exist, because
199 # the stat64() syscall fails.
200 # this causes error messages when unpacking symlinks in tarballs.
201 my ($err) = syscall(&SYS_lchown, $fn, $uid, $gid, $fn) ? -$! : 0;
207 my ($fn) = append_root(shift);
208 return -ENOENT() unless tagged($fn);
210 return err(chmod($mode, $fn));
214 debug("x_truncate ");
215 my ($fn) = append_root(shift);
216 return -ENOENT() unless tagged($fn);
217 return err(truncate($fn, shift));
222 my ($fn) = append_root($_[0]);
223 return -ENOENT() unless tagged($fn);
224 return err(utime($_[1], $_[2], $fn));
229 my $dir = append_root(shift);
230 return -ENOENT() unless tagged($dir);
231 return err(detag($dir));
236 my $name = append_root(shift);
237 my($bsize, $frsize, $blocks, $bfree, $bavail,
238 $files, $ffree, $favail, $fsid, $basetype, $flag,
239 $namemax, $fstr) = statvfs($real_root) || return -$!;
240 return ($namemax, $files, $ffree, $blocks, $bavail, $bsize);
243 # If you run the script directly, it will run fusermount, which will in turn
244 # re-run this script. Hence the funky semantics.
246 # Parse command-line arguments
250 $real_root = shift(@ARGV);
251 $mountpoint = shift(@ARGV);
256 mountpoint=>$mountpoint,
258 getattr =>"main::x_getattr",
259 readlink=>"main::x_readlink",
260 getdir =>"main::x_getdir",
261 mknod =>"main::x_mknod",
262 mkdir =>"main::x_mkdir",
263 unlink =>"main::x_unlink",
264 rmdir =>"main::x_rmdir",
265 symlink =>"main::x_symlink",
266 rename =>"main::x_rename",
267 link =>"main::x_link",
268 chmod =>"main::x_chmod",
269 chown =>"main::x_chown",
270 truncate=>"main::x_truncate",
271 utime =>"main::x_utime",
272 open =>"main::x_open",
273 read =>"main::x_read",
274 write =>"main::x_write",
275 statfs =>"main::x_statfs",