rfuse 1.0.0 → 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.yardopts +2 -0
- data/CHANGES.md +9 -0
- data/README.md +2 -2
- data/Rakefile +1 -3
- data/ext/rfuse/extconf.rb +5 -0
- data/ext/rfuse/rfuse.c +39 -16
- data/lib/rfuse/compat.rb +20 -0
- data/lib/rfuse/version.rb +1 -1
- data/lib/rfuse.rb +13 -1
- data/rfuse.gemspec +4 -1
- data/spec/basic_spec.rb +69 -38
- data/spec/spec_helper.rb +12 -0
- metadata +8 -6
data/.yardopts
ADDED
data/CHANGES.md
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
1.0.2- 2012/08/09
|
2
|
+
-----------------
|
3
|
+
|
4
|
+
Support Ruby 1.8 (tested with 1.8.7)
|
5
|
+
|
6
|
+
Bugfixes
|
7
|
+
|
8
|
+
* {RFuse::Fuse#utimens} fixed to correctly convert time to nanoseconds
|
9
|
+
* Exceptions in {RFuse::Fuse#getattr} fixed to output backtrace etc
|
1
10
|
|
2
11
|
1.0.0
|
3
12
|
----------------
|
data/README.md
CHANGED
@@ -3,7 +3,7 @@ RFuse
|
|
3
3
|
|
4
4
|
http://rubygems.org/gems/rfuse
|
5
5
|
|
6
|
-
FUSE bindings for Ruby
|
6
|
+
FUSE bindings for Ruby
|
7
7
|
|
8
8
|
FUSE (Filesystem in USErspace) is a simple interface for userspace programs to export a virtual filesystem to the linux kernel. FUSE aims to provide a secure method for non privileged users to create and mount their own filesystem implementations.
|
9
9
|
|
@@ -14,7 +14,7 @@ For a more ruby-ish API for creating filesystems see {http://rubygems.org/gems/r
|
|
14
14
|
Dependencies
|
15
15
|
--------------
|
16
16
|
|
17
|
-
* Ruby 1.9
|
17
|
+
* Ruby 1.8 or 1.9
|
18
18
|
* Fuse 2.8
|
19
19
|
|
20
20
|
Installation
|
data/Rakefile
CHANGED
data/ext/rfuse/extconf.rb
CHANGED
data/ext/rfuse/rfuse.c
CHANGED
@@ -14,7 +14,6 @@
|
|
14
14
|
#include <sys/xattr.h>
|
15
15
|
//#endif
|
16
16
|
|
17
|
-
#include <ruby/encoding.h>
|
18
17
|
#include "helper.h"
|
19
18
|
#include "intern_rfuse.h"
|
20
19
|
#include "filler.h"
|
@@ -24,13 +23,32 @@
|
|
24
23
|
#include "pollhandle.h"
|
25
24
|
#include "bufferwrapper.h"
|
26
25
|
|
26
|
+
#ifdef HAVE_RUBY_ENCODING_H
|
27
|
+
#include <ruby/encoding.h>
|
28
|
+
#endif
|
29
|
+
|
30
|
+
#ifndef HAVE_RB_ERRINFO
|
31
|
+
static VALUE rb_errinfo()
|
32
|
+
{
|
33
|
+
return ruby_errinfo;
|
34
|
+
}
|
35
|
+
#endif
|
27
36
|
|
28
37
|
static VALUE mRFuse;
|
29
38
|
static VALUE eRFuse_Error;
|
30
39
|
|
40
|
+
static VALUE rb_filesystem_encode(VALUE str)
|
41
|
+
{
|
42
|
+
#ifdef HAVE_RUBY_ENCODING_H
|
43
|
+
return rb_enc_associate(str,rb_filesystem_encoding());
|
44
|
+
#else
|
45
|
+
return str;
|
46
|
+
#endif
|
47
|
+
}
|
48
|
+
|
31
49
|
static int unsafe_return_error(VALUE *args)
|
32
50
|
{
|
33
|
-
|
51
|
+
|
34
52
|
if (rb_respond_to(rb_errinfo(),rb_intern("errno"))) {
|
35
53
|
//We expect these and they get passed on the fuse so be quiet...
|
36
54
|
return rb_funcall(rb_errinfo(),rb_intern("errno"),0);
|
@@ -73,7 +91,7 @@ static void init_context_path_args(VALUE *args,struct fuse_context *ctx,const ch
|
|
73
91
|
args[0] = ctx->private_data;
|
74
92
|
args[1] = wrap_context(ctx);
|
75
93
|
args[2] = rb_str_new2(path);
|
76
|
-
|
94
|
+
rb_filesystem_encode(args[2]);
|
77
95
|
}
|
78
96
|
/*
|
79
97
|
@overload readdir(context,path,filler,offset,ffi)
|
@@ -275,13 +293,14 @@ static int rf_getattr(const char *path, struct stat *stbuf)
|
|
275
293
|
|
276
294
|
res=rb_protect((VALUE (*)())unsafe_getattr,(VALUE) args,&error);
|
277
295
|
|
278
|
-
if (res == Qnil) {
|
279
|
-
return -ENOENT;
|
280
|
-
}
|
281
296
|
if (error)
|
282
297
|
{
|
283
298
|
return -(return_error(ENOENT));
|
284
299
|
}
|
300
|
+
|
301
|
+
if (res == Qnil) {
|
302
|
+
return -ENOENT;
|
303
|
+
}
|
285
304
|
else
|
286
305
|
{
|
287
306
|
rstat2stat(res,stbuf);
|
@@ -733,7 +752,7 @@ static int rf_symlink(const char *path,const char *as)
|
|
733
752
|
init_context_path_args(args,ctx,path);
|
734
753
|
|
735
754
|
args[3]=rb_str_new2(as);
|
736
|
-
|
755
|
+
rb_filesystem_encode(args[3]);
|
737
756
|
|
738
757
|
res=rb_protect((VALUE (*)())unsafe_symlink,(VALUE) args,&error);
|
739
758
|
|
@@ -768,7 +787,7 @@ static int rf_rename(const char *path,const char *as)
|
|
768
787
|
init_context_path_args(args,ctx,path);
|
769
788
|
|
770
789
|
args[3]=rb_str_new2(as);
|
771
|
-
|
790
|
+
rb_filesystem_encode(args[3]);
|
772
791
|
|
773
792
|
res=rb_protect((VALUE (*)())unsafe_rename,(VALUE) args,&error);
|
774
793
|
|
@@ -802,7 +821,7 @@ static int rf_link(const char *path,const char * as)
|
|
802
821
|
struct fuse_context *ctx=fuse_get_context();
|
803
822
|
init_context_path_args(args,ctx,path);
|
804
823
|
args[3]=rb_str_new2(as);
|
805
|
-
|
824
|
+
rb_filesystem_encode(args[3]);
|
806
825
|
res=rb_protect((VALUE (*)())unsafe_link,(VALUE) args,&error);
|
807
826
|
|
808
827
|
return error ? -(return_error(ENOENT)) : 0;
|
@@ -1585,17 +1604,17 @@ static int rf_utimens(const char * path, const struct timespec tv[2])
|
|
1585
1604
|
struct fuse_context *ctx = fuse_get_context();
|
1586
1605
|
init_context_path_args(args,ctx,path);
|
1587
1606
|
|
1588
|
-
// tv_sec *
|
1607
|
+
// tv_sec * 1000000000 + tv_nsec
|
1589
1608
|
args[3] = rb_funcall(
|
1590
1609
|
rb_funcall(
|
1591
|
-
INT2NUM(tv[0].tv_sec), rb_intern("*"), 1, INT2NUM(
|
1610
|
+
INT2NUM(tv[0].tv_sec), rb_intern("*"), 1, INT2NUM(1000000000)
|
1592
1611
|
),
|
1593
1612
|
rb_intern("+"), 1, INT2NUM(tv[0].tv_nsec)
|
1594
1613
|
);
|
1595
1614
|
|
1596
1615
|
args[4] = rb_funcall(
|
1597
1616
|
rb_funcall(
|
1598
|
-
INT2NUM(tv[1].tv_sec), rb_intern("*"), 1, INT2NUM(
|
1617
|
+
INT2NUM(tv[1].tv_sec), rb_intern("*"), 1, INT2NUM(1000000000)
|
1599
1618
|
),
|
1600
1619
|
rb_intern("+"), 1, INT2NUM(tv[1].tv_nsec)
|
1601
1620
|
);
|
@@ -1675,7 +1694,7 @@ static int rf_ioctl(const char *path, int cmd, void *arg,
|
|
1675
1694
|
int error = 0;
|
1676
1695
|
|
1677
1696
|
args[0] = rb_str_new2(path);
|
1678
|
-
|
1697
|
+
rb_filesystem_encode(args[0]);
|
1679
1698
|
args[1] = INT2NUM(cmd);
|
1680
1699
|
args[2] = wrap_buffer(arg);
|
1681
1700
|
args[3] = get_file_info(ffi);
|
@@ -1715,7 +1734,7 @@ static int rf_poll(const char *path, struct fuse_file_info *ffi,
|
|
1715
1734
|
int error = 0;
|
1716
1735
|
|
1717
1736
|
args[0] = rb_str_new2(path);
|
1718
|
-
|
1737
|
+
rb_filesystem_encode(args[0]);
|
1719
1738
|
args[1] = get_file_info(ffi);
|
1720
1739
|
args[2] = wrap_pollhandle(ph);
|
1721
1740
|
args[3] = INT2NUM(*reventsp);
|
@@ -1756,7 +1775,11 @@ VALUE rf_unmount(VALUE self)
|
|
1756
1775
|
struct intern_fuse *inf;
|
1757
1776
|
Data_Get_Struct(self,struct intern_fuse,inf);
|
1758
1777
|
|
1759
|
-
|
1778
|
+
rb_funcall(self,rb_intern("ruby_unmount"),0);
|
1779
|
+
|
1780
|
+
if (inf->fuse != NULL) {
|
1781
|
+
fuse_exit(inf->fuse);
|
1782
|
+
}
|
1760
1783
|
|
1761
1784
|
if (inf->fc != NULL) {
|
1762
1785
|
fuse_unmount(inf->mountpoint, inf->fc);
|
@@ -1773,7 +1796,7 @@ VALUE rf_mountname(VALUE self)
|
|
1773
1796
|
struct intern_fuse *inf;
|
1774
1797
|
Data_Get_Struct(self,struct intern_fuse,inf);
|
1775
1798
|
VALUE result = rb_str_new2(inf->mountpoint);
|
1776
|
-
|
1799
|
+
rb_filesystem_encode(result);
|
1777
1800
|
|
1778
1801
|
return result;
|
1779
1802
|
}
|
data/lib/rfuse/compat.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
|
2
|
+
unless IO.instance_methods.include?(:autoclose?)
|
3
|
+
class IO
|
4
|
+
def self.for_fd(fd, mode_string, options = {})
|
5
|
+
IO.new(fd,mode_string)
|
6
|
+
end
|
7
|
+
|
8
|
+
def autoclose?
|
9
|
+
true
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
unless Time.instance_methods.include?(:nsec)
|
15
|
+
class Time
|
16
|
+
def nsec
|
17
|
+
usec * 1000
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/rfuse/version.rb
CHANGED
data/lib/rfuse.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'fcntl'
|
2
2
|
require 'rfuse/version'
|
3
3
|
require 'rfuse/rfuse'
|
4
|
+
require 'rfuse/compat'
|
4
5
|
|
5
6
|
# Ruby FUSE (Filesystem in USErspace) binding
|
6
7
|
module RFuse
|
@@ -30,7 +31,6 @@ module RFuse
|
|
30
31
|
#
|
31
32
|
# @return [void]
|
32
33
|
# @raise [RFuse::Error] if already running or not mounted
|
33
|
-
#
|
34
34
|
def loop()
|
35
35
|
raise RFuse::Error, "Already running!" if @running
|
36
36
|
raise RFuse::Error, "FUSE not mounted" unless mounted?
|
@@ -80,6 +80,18 @@ module RFuse
|
|
80
80
|
# ruby to do anything with it during GC
|
81
81
|
@fuse_io = IO.for_fd(fd(),"r",:autoclose => false)
|
82
82
|
end
|
83
|
+
|
84
|
+
# Called by C unmount before doing all the FUSE stuff
|
85
|
+
def ruby_unmount
|
86
|
+
@pr.close if @pr && !@pr.closed?
|
87
|
+
@pw.close if @pw && !@pw.closed?
|
88
|
+
|
89
|
+
# Ideally we want this IO to avoid autoclosing at GC, but
|
90
|
+
# in Ruby 1.8 we have no way to do that. A work around is to close
|
91
|
+
# the IO here. FUSE won't necessarily like that but it is the best
|
92
|
+
# we can do
|
93
|
+
@fuse_io.close() if @fuse_io && !@fuse_io.closed? && @fuse_io.autoclose?
|
94
|
+
end
|
83
95
|
end
|
84
96
|
|
85
97
|
#This class is useful to make your filesystem implementation
|
data/rfuse.gemspec
CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |s|
|
|
10
10
|
s.email = ["grant@lastweekend.com.au"]
|
11
11
|
s.homepage = "http://rubygems.org/gems/rfuse"
|
12
12
|
s.summary = %q{Ruby language binding for FUSE}
|
13
|
-
s.description = %q{
|
13
|
+
s.description = %q{Write userspace filesystems in Ruby}
|
14
14
|
|
15
15
|
s.files = `git ls-files`.split("\n")
|
16
16
|
s.extensions = 'ext/rfuse/extconf.rb'
|
@@ -18,6 +18,9 @@ Gem::Specification.new do |s|
|
|
18
18
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
19
|
s.require_paths = ["lib"]
|
20
20
|
|
21
|
+
s.has_rdoc = 'yard'
|
22
|
+
s.extra_rdoc_files = 'CHANGES.md'
|
23
|
+
|
21
24
|
s.add_development_dependency("rake")
|
22
25
|
s.add_development_dependency("rspec")
|
23
26
|
s.add_development_dependency("yard")
|
data/spec/basic_spec.rb
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
require 'pathname'
|
3
|
+
require 'tempfile'
|
3
4
|
|
4
5
|
describe RFuse::Fuse do
|
5
|
-
|
6
|
+
|
6
7
|
let(:dir_stat) { RFuse::Stat.directory(0444) }
|
7
8
|
let(:file_stat) { RFuse::Stat.file(0444) }
|
8
9
|
let!(:mockfs) { m = mock("fuse"); m.stub(:getattr).and_return(nil); m }
|
@@ -26,7 +27,7 @@ describe RFuse::Fuse do
|
|
26
27
|
fuse.mounted?.should be_false
|
27
28
|
lambda { fuse.loop }.should raise_error(RFuse::Error)
|
28
29
|
end
|
29
|
-
|
30
|
+
|
30
31
|
it "should handle a Pathname as a mountpoint" do
|
31
32
|
fuse = RFuse::FuseDelegator.new(mockfs,Pathname.new(mountpoint))
|
32
33
|
fuse.mounted?.should be_true
|
@@ -57,7 +58,7 @@ describe RFuse::Fuse do
|
|
57
58
|
|
58
59
|
mockfs.should_receive(:readdir) do | ctx, path, filler,offset,ffi |
|
59
60
|
filler.push("hello",nil,0)
|
60
|
-
|
61
|
+
filler.push("world",nil,0)
|
61
62
|
end
|
62
63
|
|
63
64
|
with_fuse(mountpoint,mockfs) do
|
@@ -83,38 +84,50 @@ describe RFuse::Fuse do
|
|
83
84
|
|
84
85
|
context "timestamps" do
|
85
86
|
|
87
|
+
|
86
88
|
it "should support stat with subsecond resolution" do
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
stat.mtime.should == mtime
|
115
|
-
end
|
89
|
+
testns = Tempfile.new("testns")
|
90
|
+
stat = File.stat(testns.path)
|
91
|
+
|
92
|
+
# so if this Ruby has usec resolution for File.stat
|
93
|
+
# we'd expect to skip this test 1 in 100,000 times...
|
94
|
+
no_usecs = (stat.mtime.usec == 0)
|
95
|
+
|
96
|
+
if no_usecs
|
97
|
+
puts "Skipping due to no usec resolution for File.stat"
|
98
|
+
else
|
99
|
+
atime,mtime,ctime = usec_times(60,600,3600)
|
100
|
+
|
101
|
+
file_stat.atime = atime
|
102
|
+
file_stat.mtime = mtime
|
103
|
+
file_stat.ctime = ctime
|
104
|
+
|
105
|
+
# ruby can't set file times with ns res, o we are limited to usecs
|
106
|
+
mockfs.stub(:getattr).with(anything(),"/nanos").and_return(file_stat)
|
107
|
+
|
108
|
+
with_fuse(mountpoint,mockfs) do
|
109
|
+
stat = File.stat("#{mountpoint}/nanos")
|
110
|
+
stat.atime.usec.should == atime.usec
|
111
|
+
stat.atime.should == atime
|
112
|
+
stat.ctime.should == ctime
|
113
|
+
stat.mtime.should == mtime
|
114
|
+
end
|
115
|
+
end
|
116
116
|
end
|
117
117
|
|
118
|
+
it "should set file access and modification times subsecond resolution" do
|
119
|
+
atime,mtime = usec_times(60,600)
|
120
|
+
|
121
|
+
atime_ns = (atime.to_i * (10**9)) + (atime.nsec)
|
122
|
+
mtime_ns = (mtime.to_i * (10**9)) + (mtime.nsec)
|
123
|
+
|
124
|
+
mockfs.stub(:getattr).with(anything(),"/usec").and_return(file_stat)
|
125
|
+
mockfs.should_receive(:utimens).with(anything,"/usec",atime_ns,mtime_ns)
|
126
|
+
|
127
|
+
with_fuse(mountpoint,mockfs) do
|
128
|
+
File.utime(atime,mtime,"#{mountpoint}/usec").should == 1
|
129
|
+
end
|
130
|
+
end
|
118
131
|
it "should set file access and modification times" do
|
119
132
|
|
120
133
|
atime = Time.now()
|
@@ -134,12 +147,12 @@ describe RFuse::Fuse do
|
|
134
147
|
|
135
148
|
it "should create files" do
|
136
149
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
150
|
+
mockfs.stub(:getattr).with(anything(),"/newfile").and_return(nil,file_stat)
|
151
|
+
mockfs.should_receive(:mknod).with(anything(),"/newfile",file_mode(0644),0,0)
|
152
|
+
|
153
|
+
with_fuse(mountpoint,mockfs) do
|
141
154
|
File.open("#{mountpoint}/newfile","w",0644) { |f| }
|
142
|
-
|
155
|
+
end
|
143
156
|
end
|
144
157
|
|
145
158
|
# ruby doesn't seem to have a native method to create these
|
@@ -158,7 +171,7 @@ describe RFuse::Fuse do
|
|
158
171
|
end
|
159
172
|
|
160
173
|
}
|
161
|
-
|
174
|
+
|
162
175
|
reads = 0
|
163
176
|
mockfs.stub(:read) { |ctx,path,size,offset,ffi|
|
164
177
|
reads += 2
|
@@ -173,4 +186,22 @@ describe RFuse::Fuse do
|
|
173
186
|
end
|
174
187
|
end
|
175
188
|
end
|
189
|
+
|
190
|
+
context "exceptions" do
|
191
|
+
|
192
|
+
it "should capture exceptions appropriately" do
|
193
|
+
|
194
|
+
mockfs.should_receive(:getattr).with(anything(),"/exceptions").and_raise(RuntimeError)
|
195
|
+
|
196
|
+
with_fuse(mountpoint,mockfs) do
|
197
|
+
begin
|
198
|
+
File.stat("#{mountpoint}/exceptions")
|
199
|
+
raise "should not get here"
|
200
|
+
rescue Errno::ENOENT
|
201
|
+
#all good
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
end
|
176
207
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -47,5 +47,17 @@ module RFuseHelper
|
|
47
47
|
def tempmount()
|
48
48
|
Dir.mktmpdir("rfuse-spec")
|
49
49
|
end
|
50
|
+
|
51
|
+
# Generate a set of times with non-zero usec values
|
52
|
+
def usec_times(*increments)
|
53
|
+
increments.collect { |inc|
|
54
|
+
begin
|
55
|
+
time = Time.now() + inc
|
56
|
+
sleep(0.001)
|
57
|
+
end until time.usec != 0
|
58
|
+
time
|
59
|
+
}
|
60
|
+
end
|
61
|
+
|
50
62
|
end
|
51
63
|
include RFuseHelper
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rfuse
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-08-
|
12
|
+
date: 2012-08-09 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rake
|
@@ -91,16 +91,17 @@ dependencies:
|
|
91
91
|
- - ! '>='
|
92
92
|
- !ruby/object:Gem::Version
|
93
93
|
version: '0'
|
94
|
-
description:
|
95
|
-
for Ruby 1.9.2.
|
94
|
+
description: Write userspace filesystems in Ruby
|
96
95
|
email:
|
97
96
|
- grant@lastweekend.com.au
|
98
97
|
executables: []
|
99
98
|
extensions:
|
100
99
|
- ext/rfuse/extconf.rb
|
101
|
-
extra_rdoc_files:
|
100
|
+
extra_rdoc_files:
|
101
|
+
- CHANGES.md
|
102
102
|
files:
|
103
103
|
- .gitignore
|
104
|
+
- .yardopts
|
104
105
|
- CHANGES.md
|
105
106
|
- Gemfile
|
106
107
|
- LICENSE
|
@@ -127,6 +128,7 @@ files:
|
|
127
128
|
- ext/rfuse/rfuse_mod.c
|
128
129
|
- lib/rfuse-ng.rb
|
129
130
|
- lib/rfuse.rb
|
131
|
+
- lib/rfuse/compat.rb
|
130
132
|
- lib/rfuse/version.rb
|
131
133
|
- lib/rfuse_ng.rb
|
132
134
|
- rfuse.gemspec
|
@@ -161,4 +163,4 @@ signing_key:
|
|
161
163
|
specification_version: 3
|
162
164
|
summary: Ruby language binding for FUSE
|
163
165
|
test_files: []
|
164
|
-
has_rdoc:
|
166
|
+
has_rdoc: yard
|