Skip to content

Instantly share code, notes, and snippets.

@nilsding
Created November 17, 2014 18:40
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nilsding/b42038aef85c3adc0d66 to your computer and use it in GitHub Desktop.
Save nilsding/b42038aef85c3adc0d66 to your computer and use it in GitHub Desktop.
JSONfs - the JSON file system
#!/usr/bin/ruby
# JSONfs - the JSON file system
# (c) 2014 nilsding
# License: GPLv2
#
# Requires RFuse, install it via `gem install rfuse`
#
# Based on the RFuse sample file system:
# https://github.com/lwoggardner/rfuse/blob/master/sample/test-ruby.rb
# (c) 2014 lwoggardner
#
# Usage:
# JSON=./a_json_file.json ruby jsonfs.rb mountpoint
require "rfuse"
require "json"
require "yaml"
if ENV['JSON'].nil?
raise "please set the JSON environment variable to the path of a valid " \
"JSON file"
end
$json = {}
File.open ENV['JSON'], 'r' do |f|
$json = JSON.parse f.read
end
class JSONDir < Hash
attr_accessor :name, :mode, :actime, :modtime, :uid, :gid
def initialize(name, mode)
@uid = 0
@gid = 0
@actime = Time.now
@modtime = Time.now
@xattr = Hash.new
@name = name
@mode = mode
end
def stat
RFuse::Stat.directory(mode, uid: uid, gid: gid, atime: actime,
mtime: modtime, size: size)
end
def listxattr
@xattr.keys
end
def setxattr(name, value, flag)
@xattr[name] = value # TODO: don't ignore flag
end
def getxattr(name)
@xattr[name]
end
def removexattr(name)
@xattr.delete(name)
end
def size
48 #for testing only
end
def isdir
true
end
def insert_obj(obj, path)
d = self.search File.dirname path
if d.isdir
d[obj.name] = obj
else
raise Errno::ENOTDIR.new d.name
end
d
end
def remove_obj path
d = self.search File.dirname path
d.delete File.basename path
end
def search path
p = path.split('/').delete_if { |x| x == '' }
if p.length==0
self
else
self.follow p
end
end
def follow(path_array)
if path_array.length == 0
self
else
d = self[path_array.shift]
if d
d.follow(path_array)
else
raise Errno::ENOENT.new
end
end
end
def to_s
"Dir: " + @name + "(" + @mode.to_s + ")"
end
end
class JSONFile
attr_accessor :name, :mode, :actime, :modtime, :uid, :gid, :content
def initialize(name, mode, uid, gid)
@actime=0
@modtime=0
@xattr=Hash.new
@content=""
@uid=uid
@gid=gid
@name=name
@mode=mode
end
def stat
RFuse::Stat.file(mode, uid: uid, gid: gid, atime: actime,
mtime: modtime, size: size)
end
def listxattr
@xattr.keys
end
def setxattr(name,value,flag)
@xattr[name]=value #TODO:don't ignore flag
end
def getxattr(name)
@xattr[name]
end
def removexattr(name)
@xattr.delete(name)
end
def size
content.size
end
def isdir
false
end
def follow(path_array)
if path_array.length != 0
raise Errno::ENOTDIR.new
else
self
end
end
def to_s
"File: " + @name + "(" + @mode.to_s + ")"
end
end
class JSONfs
def initialize(root)
@root = root
@blocks = 0
end
def read_json_obj(o, path = '/', is_hash = true)
# puts "read_json_obj(#{o.class.to_s}, #{path.inspect}, #{is_hash})"
mkdir nil, path, 0777 unless path == '/'
@blocks += 1
if is_hash
print "."
o.each do |k, v|
decide path, v, k
end
else
print ","
o.each_with_index do |elem, i|
decide path, elem, sprintf("%04d", i)
end
end
end
def decide(path, v, k)
# puts "decide(#{path.inspect}, #{v.class.to_s}, #{k.inspect})"
@blocks += 1
file = File.expand_path(k.to_s, path)
if v.is_a? Array
read_json_obj v, file, false
elsif v.is_a? Hash
read_json_obj v, file
elsif v.is_a? Numeric
@root.insert_obj(
JSONFile.new(File.basename("#{file}"), 0777, 0, 0),"#{file}"
)
write nil, "#{file}", v.to_s, 0, nil
elsif v.is_a? String
@root.insert_obj(
JSONFile.new(File.basename("#{file}.txt"), 0777, 0, 0), "#{file}.txt"
)
write nil, "#{file}.txt", v.to_s, 0, nil
elsif v.is_a? NilClass
# TODO
elsif v.is_a? TrueClass
# TODO
elsif v.is_a? FalseClass
# TODO
end
end
def readdir(ctx, path, filler, offset, ffi)
d = @root.search path
if d.isdir
d.each do |name, obj|
filler.push name, obj.stat, 0
end
else
raise Errno::ENOTDIR.new path
end
end
def getattr(ctx, path)
d = @root.search path
d.stat
end
def mkdir(ctx, path, mode)
raise Errno::EACCES.new unless @reading
@root.insert_obj JSONDir.new(File.basename(path), mode), path
end
def mknod(ctx, path, mode, major, minor)
raise Errno::EACCES.new unless @reading
@root.insert_obj(
JSONFile.new(File.basename(path), mode, ctx.uid, ctx.gid), path
)
end
def open(ctx,path,ffi)
end
#def release(ctx, path, fi)
#end
#def flush(ctx, path, fi)
#end
def chmod(ctx, path, mode)
raise Errno::EACCES.new unless @reading
d = @root.search path
d.mode = mode
end
def chown(ctx, path, uid, gid)
raise Errno::EACCES.new unless @reading
d = @root.search path
d.uid = uid
d.gid = gid
end
def truncate(ctx, path, offset)
d = @root.search path
d.content = d.content[0..offset]
end
def utime(ctx, path, actime, modtime)
d = @root.search path
d.actime = actime
d.modtime = modtime
end
def unlink(ctx, path)
raise Errno::EACCES.new unless @reading
@root.remove_obj path
end
def rmdir(ctx, path)
raise Errno::EACCES.new unless @reading
@root.remove_obj path
end
#def symlink(ctx, path, as)
#end
def rename(ctx, path, as)
raise Errno::EACCES.new unless @reading
d = @root.search path
@root.remove_obj path
@root.insert_obj d, path
end
#def link(ctx, path, as)
#end
def read(ctx, path, size, offset, fi)
d = @root.search path
if d.isdir
raise Errno::EISDIR.new path
nil
else
d.content[offset..offset + size - 1]
end
end
def write(ctx, path, buf, offset, fi)
raise Errno::ENOSPC.new unless @reading
d = @root.search path
if d.isdir
raise Errno::EISDIR.new path
else
d.content[offset..offset + buf.length - 1] = buf
end
buf.length
end
def setxattr(ctx, path, name, value, size, flags)
raise Errno::EACCES.new unless @reading
d = @root.search path
d.setxattr path, name, value, size, flags
end
def getxattr(ctx, path, name)
d = @root.search path
if d
value = d.getxattr name
unless value
value = ""
#raise Errno::ENOENT.new # TODO raise the correct error :
#NOATTR which is not implemented in Linux/glibc
end
else
raise Errno::ENOENT.new
end
value
end
def listxattr(ctx, path)
d = @root.search path
value = d.listxattr
value
end
def removexattr(ctx, path, name)
raise Errno::EACCES.new unless @reading
d = @root.search path
d.removexattr name
end
#def opendir(ctx, path, ffi)
#end
#def releasedir(ctx, path, ffi)
#end
#def fsyncdir(ctx, path, meta, ffi)
#end
def statfs(ctx, path)
s = RFuse::StatVfs.new()
s.f_bsize = 1024
s.f_frsize = 1024
s.f_blocks = @blocks
s.f_bfree = 0
s.f_bavail = 0
s.f_files = 10000
s.f_ffree = 9900
s.f_favail = 9900
s.f_fsid = 23423
s.f_flag = 0
s.f_namemax = 10000
s
end
def ioctl(ctx, path, cmd, arg, ffi, flags, data)
# TODO: test this
puts "*** IOCTL: command: #{cmd}"
end
def poll(ctx, path, ffi, ph, reventsp)
puts "*** POLL: #{path}"
# This is how we notify the caller if something happens:
ph.notifyPoll();
# when the GC harvests the object it calls fuse_pollhandle_destroy
# by itself.
end
def init(ctx, rfuseconninfo)
puts "JSONfs started"
puts "init called"
puts "proto_major:#{rfuseconninfo.proto_major}"
print "reading JSON"
@reading = true
read_json_obj $json
@reading = nil
$json = nil
puts " done"
end
end
if ARGV.length == 0
puts "Usage: #{$0} mountpoint [mount_options...]"
puts
puts " -h for supported options"
exit 1
end
fs = JSONfs.new JSONDir.new("", 0777)
fo = RFuse::FuseDelegator.new fs, *ARGV
if fo.mounted?
trap "TERM" do
puts "Caught SIGTERM"
fo.exit
end
trap "INT" do
puts "Caught SIGINT"
fo.exit
end
begin
fo.loop
rescue
puts "Error: #{$!}"
ensure
fo.unmount if fo.mounted?
puts "Unmounted #{ARGV[0]}"
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment