rfuse 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|