fusefs-0.7.0/0000775000175000017500000000000011745335420011503 5ustar paulpaulfusefs-0.7.0/metadata.yml0000664000175000017500000000261711745335420014014 0ustar paulpaul--- !ruby/object:Gem::Specification name: fusefs version: !ruby/object:Gem::Version version: 0.7.0 platform: ruby authors: - Shane autorequire: bindir: bin cert_chain: [] date: 2010-03-15 00:00:00 +00:00 default_executable: dependencies: [] description: Gemified email: shane@duairc.com executables: [] extensions: - ext/extconf.rb extra_rdoc_files: - LICENSE - README - TODO files: - .document - .gitignore - API.txt - Changes.txt - LICENSE - README - Rakefile - TODO - VERSION - ext/MANIFEST - ext/extconf.rb - ext/fusefs_fuse.c - ext/fusefs_fuse.h - ext/fusefs_lib.c - fusefs.gemspec - hello.sh - lib/fusefs.rb - sample/demo.rb - sample/dictfs.rb - sample/hello.rb - sample/openurifs.rb - sample/railsfs.rb - sample/sqlfs.rb - sample/yamlfs.rb - test/fusefs_test.rb - test/test_helper.rb has_rdoc: true homepage: http://github.com/duairc/fusefs licenses: [] post_install_message: rdoc_options: - --charset=UTF-8 require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: "0" version: required_rubygems_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: "0" version: requirements: [] rubyforge_project: rubygems_version: 1.3.5 signing_key: specification_version: 3 summary: fusefs test_files: - test/test_helper.rb - test/fusefs_test.rb fusefs-0.7.0/test/0000775000175000017500000000000011745335420012462 5ustar paulpaulfusefs-0.7.0/test/test_helper.rb0000644000175000017500000000033111745335420015320 0ustar paulpaulrequire 'rubygems' require 'test/unit' require 'shoulda' $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) $LOAD_PATH.unshift(File.dirname(__FILE__)) require 'fusefs' class Test::Unit::TestCase end fusefs-0.7.0/test/fusefs_test.rb0000644000175000017500000000034311745335420015337 0ustar paulpaulrequire 'test_helper' class FusefsTest < Test::Unit::TestCase should "probably rename this file and start testing for real" do flunk "hey buddy, you should probably rename this file and start testing for real" end end fusefs-0.7.0/sample/0000775000175000017500000000000011745335420012764 5ustar paulpaulfusefs-0.7.0/sample/yamlfs.rb0000644000175000017500000000673711745335420014617 0ustar paulpaul# yamlfs.rb # require "rubygems" require 'fusefs' include FuseFS require 'yaml' class YAMLFS < FuseFS::FuseDir def initialize(filename) @filename = filename begin @fs = YAML.load(IO.read(filename)) rescue Exception @fs = Hash.new() end end def save File.open(@filename,'w') do |fout| fout.puts(YAML.dump(@fs)) end end def contents(path) items = scan_path(path) node = items.inject(@fs) do |node,item| item ? node[item] : node end node.keys.sort end def directory?(path) items = scan_path(path) node = items.inject(@fs) do |node,item| item ? node[item] : node end node.is_a?(Hash) end def file?(path) items = scan_path(path) node = items.inject(@fs) do |node,item| item ? node[item] : node end node.is_a?(String) end def touch(path) puts "#{path} has been pushed like a button!" end # File reading def read_file(path) items = scan_path(path) node = items.inject(@fs) do |node,item| item ? node[item] : node end node.to_s end def size(path) read_file(path).size end # File writing def can_write?(path) items = scan_path(path) name = items.pop # Last is the filename. node = items.inject(@fs) do |node,item| item ? node[item] : node end node.is_a?(Hash) rescue Exception => er puts "Error! #{er}" end def write_to(path,body) items = scan_path(path) name = items.pop # Last is the filename. node = items.inject(@fs) do |node,item| item ? node[item] : node end node[name] = body self.save rescue Exception => er puts "Error! #{er}" end # Delete a file def can_delete?(path) items = scan_path(path) node = items.inject(@fs) do |node,item| item ? node[item] : node end node.is_a?(String) rescue Exception => er puts "Error! #{er}" end def delete(path) items = scan_path(path) name = items.pop # Last is the filename. node = items.inject(@fs) do |node,item| item ? node[item] : node end node.delete(name) self.save rescue Exception => er puts "Error! #{er}" end # mkdirs def can_mkdir?(path) items = scan_path(path) name = items.pop # Last is the filename. node = items.inject(@fs) do |node,item| item ? node[item] : node end node.is_a?(Hash) rescue Exception => er puts "Error! #{er}" end def mkdir(path) items = scan_path(path) name = items.pop # Last is the filename. node = items.inject(@fs) do |node,item| item ? node[item] : node end node[name] = Hash.new self.save end # rmdir def can_rmdir?(path) items = scan_path(path) node = items.inject(@fs) do |node,item| item ? node[item] : node end node.is_a?(Hash) && node.empty? end def rmdir(path) items = scan_path(path) name = items.pop # Last is the filename. node = items.inject(@fs) do |node,item| item ? node[item] : node end node.delete(name) self.save end end if (File.basename($0) == File.basename(__FILE__)) if (ARGV.size < 2) puts "Usage: #{$0} " exit end dirname, yamlfile = ARGV.shift, ARGV.shift unless File.directory?(dirname) puts "Usage: #{dirname} is not a directory." exit end root = YAMLFS.new(yamlfile) # Set the root FuseFS FuseFS.set_root(root) FuseFS.mount_under(dirname, *ARGV) FuseFS.run # This doesn't return until we're unmounted. end fusefs-0.7.0/sample/sqlfs.rb0000644000175000017500000000663611745335420014452 0ustar paulpaul# sqlfs.rb # # The SQL-db proof of concept for FuseFS # # Author: Greg Millam require "rubygems" require 'fusefs' require 'mysql' class SqlFS < FuseFS::FuseDir class DBTable attr_accessor :name, :key, :fields end def initialize(host,user,pass,db) @sql = Mysql.connect(host,user,pass,db) @tables = Hash.new(nil) tables = @sql.query('show tables') tables.each do |i,| table = DBTable.new table.name = i table.fields = {} res = @sql.query("describe #{i}") res.each do |field,type,null,key,default,extra| table.fields[field] = type if (key =~ /pri/i) table.key = field end end @tables[i] = table if table.key end end def directory?(path) tname, key, field = scan_path(path) table = @tables[tname] case when tname.nil? true # This means "/" when table.nil? false when field false # Always a file when key res = @sql.query("SELECT #{table.key}, 1 FROM #{table.name} WHERE #{table.key} = '#{Mysql.escape_string(key)}'") res.num_rows > 0 # If there was a result, it exists. else true # It's just a table. end end def file?(path) tname, key, field = scan_path(path) table = @tables[tname] case when field.nil? false # Only field entries are files. when table.nil? false when ! @tables[tname].fields.include?(field) false # Invalid field. when field res = @sql.query("SELECT #{table.key}, 1 FROM #{table.name} WHERE #{table.key} = '#{Mysql.escape_string(key)}'") res.num_rows > 0 end end def can_delete?(path) # This helps editors, but we don't really use it. true end def can_write?(path) # Since this is basically only for editing files, # we just call file? file?(path) end def contents(path) # since this is only called when directory? is true, # We'll assume valid entries. tname, key, field = scan_path(path) table = @tables[tname] case when tname.nil? @tables.keys.sort # Just the tables. when key table.fields.keys.sort else # I limit to 200 so 'ls' doesn't hang all the time :D res = @sql.query("SELECT #{table.key}, 1 FROM #{table.name} ORDER BY #{table.key} LIMIT 100") ret = [] res.each do |val,one| ret << val if val.size > 0 end ret end end def write_to(path,body) # Since this is only called after can_write?(), we assume # Valid fields. tname, key, field = scan_path(path) table = @tables[tname] res = @sql.query("UPDATE #{table.name} SET #{field} = '#{Mysql.escape_string(body)}' WHERE #{table.key} = '#{key}'") end def read_file(path) # Again, as this is only called after file?, assume valid fields. tname, key, field = scan_path(path) table = @tables[tname] res = @sql.query("SELECT #{field} FROM #{table.name} WHERE #{table.key} = '#{key}'") res.fetch_row[0] end end if (File.basename($0) == File.basename(__FILE__)) if ARGV.size != 5 puts "Usage: #{$0} " exit end dirname, host, user, pass, db = ARGV if (! File.directory?(dirname)) puts "#{dirname} is not a directory" end root = SqlFS.new(host,user,pass,db) # Set the root FuseFS FuseFS.set_root(root) # root.contents("/quotes") FuseFS.mount_under(dirname) FuseFS.run # This doesn't return until we're unmounted. end fusefs-0.7.0/sample/railsfs.rb0000644000175000017500000000430611745335420014755 0ustar paulpaul#!/usr/bin/env ruby # # RailsFS, as written by _why_the_lucky_stiff # # Full instructions: # http://redhanded.hobix.com/inspect/railsfsAfterACoupleMinutesOfToolingWithFuseWhoa.html # =begin Instructions cut and paste from _why's blog: Save the railfs.rb script as script/filesys in your Rails app. (If you'd rather not cut-and-paste the above, it's here.) Now, run mkdir ~/railsmnt. Then, script/filesys ~/railsmnt. The rules are as follows: * ls ~/railsmnt will give you a list of tables. * ls ~/railsmnt/table will list IDs from the table. * cat ~/railsmnt/table/id will display a record in YAML. * vim ~/railsmnt/table/id to edit the record in YAML! =end require "rubygems" require 'fusefs' require File.dirname(__FILE__) + '/../config/environment' class RailsFS < FuseFS::FuseDir def initialize @classes = {} require 'find' Find.find( File.join(RAILS_ROOT, 'app/models') ) do |model| if /(\w+)\.rb$/ =~ model model = $1 ( @classes[model] = Kernel::const_get( Inflector.classify( model ) ) ). find :first rescue @classes.delete( model ) end end end def directory? path tname, key = scan_path path table = @classes[tname] if table.nil?; false # /table elsif key; false # /table/id else; true end end def file? path tname, key = scan_path path table = @classes[tname] key and table and table.find( key ) end def can_delete?; true end def can_write? path; file? path end def contents path tname, key = scan_path path table = @classes[tname] if tname.nil?; @classes.keys.sort # / else; table.find( :all ).map { |o| o.id.to_s } end # /table end def write_to path, body obj = YAML::load( body ) obj.save end def read_file path tname, key = scan_path path table = @classes[tname] YAML::dump( table.find( key ) ) end end if (File.basename($0) == File.basename(__FILE__)) root = RailsFS.new FuseFS.set_root(root) FuseFS.mount_under(ARGV[0]) FuseFS.run # This doesn't return until we're unmounted. end fusefs-0.7.0/sample/openurifs.rb0000644000175000017500000000176311745335420015330 0ustar paulpaul# openurifs.rb # require "rubygems" require 'fusefs' include FuseFS require 'open-uri' class OpenUriFS < FuseFS::FuseDir def contents(path) # The 'readme' file [] end def directory?(path) uri = scan_path(path) fn = uri.pop return true if fn =~ /\.(com|org|net|us|de|jp|ru|uk|biz|info)$/ return true if fn =~ /^\d+\.\d+\.\d+\.\d+$/ ! (fn =~ /\./) # Does the last item doesn't contain a '.' ? end def file?(path) !directory?(path) end def read_file(path) proto, rest = split_path(path) uri = "#{proto}://#{rest}" open(uri).read end end if (File.basename($0) == File.basename(__FILE__)) if (ARGV.size != 1) puts "Usage: #{$0} " exit end dirname = ARGV.shift unless File.directory?(dirname) puts "Usage: #{dirname} is not a directory." exit end root = OpenUriFS.new # Set the root FuseFS FuseFS.set_root(root) FuseFS.mount_under(dirname) FuseFS.run # This doesn't return until we're unmounted. end fusefs-0.7.0/sample/hello.rb0000644000175000017500000000062311745335420014413 0ustar paulpaulrequire "rubygems" require 'fusefs' class HelloDir def contents(path) ['hello.txt'] end def file?(path) path == '/hello.txt' end def read_file(path) "Hello, World!\n" end def size(path) read_file(path).size end end hellodir = HelloDir.new FuseFS.set_root( hellodir ) # Mount under a directory given on the command line. FuseFS.mount_under ARGV.shift FuseFS.run fusefs-0.7.0/sample/dictfs.rb0000644000175000017500000000345311745335420014570 0ustar paulpaul# dictfs.rb # require "rubygems" require 'fusefs' include FuseFS require 'dict' class DictFS < FuseFS::FuseDir def initialize @servers = ['dict.org','alt0.dict.org'] @database = DICT::ALL_DATABASES @strategy = 'exact' @match_strategy = DICT::DEFAULT_MATCH_STRATEGY @port = DICT::DEFAULT_PORT @dict = DICT.new(@servers, @port, false, false) @dict.client("%s v%s" % ["Dictionary","1.0"]) end def contents(path) # The 'readme' file ['readme'] end def file?(path) base, rest = split_path(path) rest.nil? # DictFS doesn't have subdirs. end def read_file(path) word, rest = split_path(path) word.downcase! if word == "readme" return %Q[ DictFS: You may not see the files, but if you cat any file here, it will look that file up on dict.org! ].lstrip end puts "Looking up #{word}" m = @dict.match(@database, @strategy, word) if m contents = [] m.each do |db,words| words.each do |w| defs = @dict.define(db,w) str = [] defs.each do |d| str << "Definition of '#{w}' (by #{d.description})" d.definition.each do |line| str << " #{line.strip}" end contents << str.join("\n") end end end contents << '' contents.join("\n") else "No dictionary definitions found\n" end end end if (File.basename($0) == File.basename(__FILE__)) if (ARGV.size != 1) puts "Usage: #{$0} " exit end dirname = ARGV.shift unless File.directory?(dirname) puts "Usage: #{dirname} is not a directory." exit end root = DictFS.new # Set the root FuseFS FuseFS.set_root(root) FuseFS.mount_under(dirname) FuseFS.run # This doesn't return until we're unmounted. end fusefs-0.7.0/sample/demo.rb0000644000175000017500000000351611745335420014240 0ustar paulpaulrequire "rubygems" require 'fusefs' include FuseFS root = MetaDir.new # if (ARGV.size != 1) # puts "Usage: #{$0} " # exit # end dirname = ARGV.shift unless File.directory?(dirname) puts "Usage: #{$0} " exit end class DirLink def initialize(dir) File.directory?(dir) or raise ArgumentError, "DirLink.initialize expects a valid directory!" @base = dir end def directory?(path) File.directory?(File.join(@base,path)) end def file?(path) File.file?(File.join(@base,path)) end def contents(path) fn = File.join(@base,path) Dir.entries(fn).map { |file| file = file.sub(/^#{fn}\/?/,'') if ['..','.'].include?(file) nil else file end }.compact.sort end def read_file(path) fn = File.join(@base,path) if File.file?(fn) IO.read(fn) else 'No such file' end end end class Counter def initialize @counter = 0 end def to_s @counter += 1 @counter.to_s + "\n" end def size @counter.to_s.size end end class Randwords def initialize(*ary) @ary = ary.flatten end def to_s @ary[rand(@ary.size)].to_s + "\n" end def size @size ||= @ary.map{|v| v.size}.max end end root.write_to('/hello',"Hello, World!\n") progress = '.' root.write_to('/progress',progress) Thread.new do 20.times do sleep 5 progress << '.' end end root.write_to('/counter',Counter.new) root.write_to('/color',Randwords.new('red','blue','green','purple','yellow','bistre','burnt sienna','jade')) root.write_to('/animal',Randwords.new('duck','dog','cat','duck billed platypus','silly fella')) root.mkdir("/#{ENV['USER']}",DirLink.new(ENV['HOME'])) # Set the root FuseFS FuseFS.set_root(root) FuseFS.mount_under(dirname, 'nolocalcaches', *ARGV) FuseFS.run # This doesn't return until we're unmounted. fusefs-0.7.0/lib/0000775000175000017500000000000011745335420012251 5ustar paulpaulfusefs-0.7.0/lib/fusefs.rb0000644000175000017500000001200711745335420014067 0ustar paulpaul# FuseFS.rb # # The ruby portion of FuseFS main library # # This includes helper functions, common uses, etc. require File.dirname(__FILE__) + '/../ext/fusefs_lib' module FuseFS @running = true def FuseFS.run fd = FuseFS.fuse_fd begin io = IO.for_fd(fd) rescue Errno::EBADF raise "fuse is not mounted" end while @running IO.select([io]) FuseFS.process end end def FuseFS.unmount system("umount #{@mountpoint}") end def FuseFS.exit @running = false end class FuseDir def split_path(path) cur, *rest = path.scan(/[^\/]+/) if rest.empty? [ cur, nil ] else [ cur, File.join(rest) ] end end def scan_path(path) path.scan(/[^\/]+/) end end class MetaDir < FuseDir def initialize @subdirs = Hash.new(nil) @files = Hash.new(nil) end # Contents of directory. def contents(path) base, rest = split_path(path) case when base.nil? (@files.keys + @subdirs.keys).sort.uniq when ! @subdirs.has_key?(base) nil when rest.nil? @subdirs[base].contents('/') else @subdirs[base].contents(rest) end end # File types def directory?(path) base, rest = split_path(path) case when base.nil? true when ! @subdirs.has_key?(base) false when rest.nil? true else @subdirs[base].directory?(rest) end end def file?(path) base, rest = split_path(path) case when base.nil? false when rest.nil? @files.has_key?(base) when ! @subdirs.has_key?(base) false else @subdirs[base].file?(rest) end end # File Reading def read_file(path) base, rest = split_path(path) case when base.nil? nil when rest.nil? @files[base].to_s when ! @subdirs.has_key?(base) nil else @subdirs[base].read_file(rest) end end # File sizing def size(path) base, rest = split_path(path) case when base.nil? 0 when rest.nil? obj = @files[base] obj.respond_to?(:size) ? obj.size : 0 when ! @subdirs.has_key?(base) 0 else dir = @subdirs[base] dir.respond_to?(:size) ? dir.size(rest) : 0 end end # Write to a file def can_write?(path) return false unless Process.uid == FuseFS.reader_uid base, rest = split_path(path) case when base.nil? true when rest.nil? true when ! @subdirs.has_key?(base) false else @subdirs[base].can_write?(rest) end end def write_to(path,file) base, rest = split_path(path) case when base.nil? false when rest.nil? @files[base] = file when ! @subdirs.has_key?(base) false else @subdirs[base].write_to(rest,file) end end # Delete a file def can_delete?(path) return false unless Process.uid == FuseFS.reader_uid base, rest = split_path(path) case when base.nil? false when rest.nil? @files.has_key?(base) when ! @subdirs.has_key?(base) false else @subdirs[base].can_delete?(rest) end end def delete(path) base, rest = split_path(path) case when base.nil? nil when rest.nil? # Delete it. @files.delete(base) when ! @subdirs.has_key?(base) nil else @subdirs[base].delete(rest) end end # Make a new directory def can_mkdir?(path) return false unless Process.uid == FuseFS.reader_uid base, rest = split_path(path) case when base.nil? false when rest.nil? ! (@subdirs.has_key?(base) || @files.has_key?(base)) when ! @subdirs.has_key?(base) false else @subdirs[base].can_mkdir?(rest) end end def mkdir(path,dir=nil) base, rest = split_path(path) case when base.nil? false when rest.nil? dir ||= MetaDir.new @subdirs[base] = dir true when ! @subdirs.has_key?(base) false else @subdirs[base].mkdir(rest,dir) end end # Delete an existing directory. def can_rmdir?(path) return false unless Process.uid == FuseFS.reader_uid base, rest = split_path(path) case when base.nil? false when rest.nil? @subdirs.has_key?(base) when ! @subdirs.has_key?(base) false else @subdirs[base].can_rmdir?(rest) end end def rmdir(path) base, rest = split_path(path) dir ||= MetaDir.new case when base.nil? false when rest.nil? @subdirs.delete(base) true when ! @subdirs.has_key?(base) false else @subdirs[base].rmdir(rest,dir) end end end end fusefs-0.7.0/hello.sh0000644000175000017500000000026011745335420013136 0ustar paulpaul#!/bin/sh mkdir -p hello ruby -rsample/hello -e "puts 'starting...'" hello & echo $? > pid sleep 1 cat hello/hello.txt # umount hello/ sleep 1 cat pid | xargs kill rmdir hello fusefs-0.7.0/fusefs.gemspec0000644000175000017500000000317211745335420014344 0ustar paulpaul# Generated by jeweler # DO NOT EDIT THIS FILE DIRECTLY # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command # -*- encoding: utf-8 -*- Gem::Specification.new do |s| s.name = %q{fusefs} s.version = "0.7.0" s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.authors = ["Shane"] s.date = %q{2010-03-15} s.description = %q{Gemified} s.email = %q{shane@duairc.com} s.extensions = ["ext/extconf.rb"] s.extra_rdoc_files = [ "LICENSE", "README", "TODO" ] s.files = [ ".document", ".gitignore", "API.txt", "Changes.txt", "LICENSE", "README", "Rakefile", "TODO", "VERSION", "ext/MANIFEST", "ext/extconf.rb", "ext/fusefs_fuse.c", "ext/fusefs_fuse.h", "ext/fusefs_lib.c", "fusefs.gemspec", "hello.sh", "lib/fusefs.rb", "sample/demo.rb", "sample/dictfs.rb", "sample/hello.rb", "sample/openurifs.rb", "sample/railsfs.rb", "sample/sqlfs.rb", "sample/yamlfs.rb", "test/fusefs_test.rb", "test/test_helper.rb" ] s.homepage = %q{http://github.com/duairc/fusefs} s.rdoc_options = ["--charset=UTF-8"] s.require_paths = ["lib"] s.rubygems_version = %q{1.3.5} s.summary = %q{fusefs} s.test_files = [ "test/test_helper.rb", "test/fusefs_test.rb" ] if s.respond_to? :specification_version then current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION s.specification_version = 3 if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then else end else end end fusefs-0.7.0/ext/0000775000175000017500000000000011745335420012303 5ustar paulpaulfusefs-0.7.0/ext/fusefs_lib.c0000644000175000017500000011353611745335420014577 0ustar paulpaul/* ruby-fuse * * A Ruby module to interact with the FUSE userland filesystem in * a Rubyish way. */ /* #define DEBUG /* */ #define FUSE_USE_VERSION 26 #define _FILE_OFFSET_BITS 64 #include #include #include #include #include #include #include #include #ifdef DEBUG #include #endif #include "fusefs_fuse.h" /* init_time * * All files will have a modified time equal to this. */ time_t init_time; /* opened_file * * FuseFS uses the opened_file list to keep files that are written to in * memory until they are closed before passing it to FuseRoot.write_to, * and file contents returned by FuseRoot.read_file until FUSE informs * us it is safe to close. */ typedef struct __opened_file_ { char *path; char *value; int modified; long writesize; long size; long zero_offset; int raw; struct __opened_file_ *next; } opened_file; typedef opened_file editor_file; static opened_file *opened_head = NULL; static editor_file *editor_head = NULL; static int file_openedP(const char *path) { opened_file *ptr; for (ptr = opened_head;ptr; ptr = ptr->next) if (!strcmp(path,ptr->path)) return 1; return 0; } /* When a file is being written to, its value starts with this much * allocated and grows by this much when necessary. */ #define FILE_GROW_SIZE 1024 /* When a file is created, the OS will first mknod it, then attempt to * fstat it immediately. We get around this by using a static path name * for the most recently mknodd'd path. */ static char *created_file = NULL; /* Ruby Constants constants */ VALUE cFuseFS = Qnil; /* FuseFS class */ VALUE cFSException = Qnil; /* Our Exception. */ VALUE FuseRoot = Qnil; /* The root object we call */ /* IDs for calling methods on objects. */ #define RMETHOD(name,cstr) \ char *c_ ## name = cstr; \ ID name; RMETHOD(id_dir_contents,"contents"); RMETHOD(id_read_file,"read_file"); RMETHOD(id_write_to,"write_to"); RMETHOD(id_delete,"delete"); RMETHOD(id_mkdir,"mkdir"); RMETHOD(id_rmdir,"rmdir"); RMETHOD(id_touch,"touch"); RMETHOD(id_size,"size"); RMETHOD(is_directory,"directory?"); RMETHOD(is_file,"file?"); RMETHOD(is_executable,"executable?"); RMETHOD(can_write,"can_write?"); RMETHOD(can_delete,"can_delete?"); RMETHOD(can_mkdir,"can_mkdir?"); RMETHOD(can_rmdir,"can_rmdir?"); RMETHOD(id_raw_open,"raw_open"); RMETHOD(id_raw_close,"raw_close"); RMETHOD(id_raw_read,"raw_read"); RMETHOD(id_raw_write,"raw_write"); RMETHOD(id_dup,"dup"); typedef unsigned long int (*rbfunc)(); /* debug() * * If #define DEBUG is enabled, then this acts as a printf to stderr */ #ifdef DEBUG static void debug(char *msg,...) { va_list ap; va_start(ap,msg); vfprintf(stderr,msg,ap); } #else // Make debug just comment out what's after it. #define debug // debug #endif /* catch_editor_files * * If this is a true value, then FuseFS will attempt to capture * editor swap files and handle them itself, so the ruby filesystem * is not passed swap files it doesn't care about. */ int handle_editor = 1; int which_editor = 0; #define EDITOR_VIM 1 #define EDITOR_EMACS 2 /* editor_fileP * * Passed a path, editor_fileP will return if it is likely to be a file * belonging to an editor. * * vim: /path/to/.somename.ext.sw* * emacs: /path/to/#somename.ext# */ static int editor_fileP(const char *path) { char *filename; editor_file *ptr; if (!handle_editor) return 0; /* Already created one */ for (ptr = editor_head ; ptr ; ptr = ptr->next) { if (strcasecmp(ptr->path,path) == 0) { return 2; } } /* Basic checks */ filename = strrchr(path,'/'); if (!filename) return 0; // No /. filename++; if (!*filename) return 0; // / is the last. /* vim */ do { // vim uses: .filename.sw? char *ptr = filename; int len; if (*ptr != '.') break; // ends with .sw? ptr = strrchr(ptr,'.'); len = strlen(ptr); // .swp or .swpx if (len != 4 && len != 5) break; if (strncmp(ptr,".sw",3) == 0) { debug(" (%s is a vim file).\n", path); which_editor = EDITOR_VIM; return 1; // It's a vim file. } } while (0); /* emacs */ do { char *ptr = filename; // Begins with a # if (*ptr != '#') break; // Ends with a # ptr = strrchr(ptr,'#'); if (!ptr) break; // the # must be the end of the filename. ptr++; if (*ptr) break; debug(" (%s is an emacs file).\n", path); which_editor = EDITOR_EMACS; return 1; } while (0); return 0; } /* rf_protected and rf_call * * Used for: protection. * * This is called by rb_protect, and will make a call using * the above rb_path and to_call ID to call the method safely * on FuseRoot. * * We call rf_call(path,method_id), and rf_call will use rb_protect * to call rf_protected, which makes the call on FuseRoot and returns * whatever the call returns. */ static VALUE rf_protected(VALUE args) { ID to_call = SYM2ID(rb_ary_shift(args)); return rb_apply(FuseRoot,to_call,args); } #define rf_call(p,m,a) \ rf_mcall(p,m, c_ ## m, a) static VALUE rf_mcall(const char *path, ID method, char *methname, VALUE arg) { int error; VALUE result; VALUE methargs; if (!rb_respond_to(FuseRoot,method)) { return Qnil; } if (arg == Qnil) { debug(" root.%s(%s)\n", methname, path ); } else { debug(" root.%s(%s,...)\n", methname, path ); } if (TYPE(arg) == T_ARRAY) { methargs = arg; } else if (arg != Qnil) { methargs = rb_ary_new(); rb_ary_push(methargs,arg); } else { methargs = rb_ary_new(); } rb_ary_unshift(methargs,rb_str_new2(path)); rb_ary_unshift(methargs,ID2SYM(method)); /* Set up the call and make it. */ result = rb_protect(rf_protected, methargs, &error); /* Did it error? */ if (error) return Qnil; return result; } /* rf_getattr * * Used when: 'ls', and before opening a file. * * FuseFS will call: directory? and file? on FuseRoot * to determine if the path in question is pointing * at a directory or file. The permissions attributes * will be 777 (dirs) and 666 (files) xor'd with FuseFS.umask */ static int rf_getattr(const char *path, struct stat *stbuf) { /* If it doesn't exist, it doesn't exist. Simple as that. */ VALUE retval; char *value; size_t len; debug("rf_getattr(%s)\n", path ); /* Zero out the stat buffer */ memset(stbuf, 0, sizeof(struct stat)); /* "/" is automatically a dir. */ if (strcmp(path,"/") == 0) { stbuf->st_mode = S_IFDIR | 0755; stbuf->st_nlink = 3; stbuf->st_uid = getuid(); stbuf->st_gid = getgid(); stbuf->st_mtime = init_time; stbuf->st_atime = init_time; stbuf->st_ctime = init_time; return 0; } /* If we created it with mknod, then it "exists" */ debug(" Checking for created file ..."); if (created_file && (strcmp(created_file,path) == 0)) { /* It's created */ debug(" created.\n"); stbuf->st_mode = S_IFREG | 0666; stbuf->st_nlink = 1 + file_openedP(path); stbuf->st_size = 0; stbuf->st_uid = getuid(); stbuf->st_gid = getgid(); stbuf->st_mtime = init_time; stbuf->st_atime = init_time; stbuf->st_ctime = init_time; return 0; } debug(" no.\n"); /* debug(" Checking file_opened ..."); if (file_openedP(path)) { debug(" opened.\n"); stbuf->st_mode = S_IFREG | 0666; stbuf->st_nlink = 1 + file_openedP(path); stbuf->st_size = 0; stbuf->st_uid = getuid(); stbuf->st_gid = getgid(); stbuf->st_mtime = init_time; stbuf->st_atime = init_time; stbuf->st_ctime = init_time; return 0; } debug(" no.\n"); */ debug(" Checking if editor file..."); switch (editor_fileP(path)) { case 2: debug(" Yes, and does exist.\n"); stbuf->st_mode = S_IFREG | 0444; stbuf->st_nlink = 1; stbuf->st_size = 0; stbuf->st_uid = getuid(); stbuf->st_gid = getgid(); stbuf->st_mtime = init_time; stbuf->st_atime = init_time; stbuf->st_ctime = init_time; return 0; case 1: debug(" Yes, but doesn't exist.\n"); return -ENOENT; default: debug("No.\n"); } /* If FuseRoot says the path is a directory, we set it 0555. * If FuseRoot says the path is a file, it's 0444. * * Otherwise, -ENOENT */ debug("Checking filetype ..."); if (RTEST(rf_call(path, is_directory,Qnil))) { debug(" directory.\n"); stbuf->st_mode = S_IFDIR | 0555; stbuf->st_nlink = 1; stbuf->st_size = 4096; stbuf->st_uid = getuid(); stbuf->st_gid = getgid(); stbuf->st_mtime = init_time; stbuf->st_atime = init_time; stbuf->st_ctime = init_time; return 0; } else if (RTEST(rf_call(path, is_file,Qnil))) { VALUE rsize; debug(" file.\n"); stbuf->st_mode = S_IFREG | 0444; if (RTEST(rf_call(path,can_write,Qnil))) { stbuf->st_mode |= 0666; } if (RTEST(rf_call(path,is_executable,Qnil))) { stbuf->st_mode |= 0111; } stbuf->st_nlink = 1 + file_openedP(path); if (RTEST(rsize = rf_call(path,id_size,Qnil)) && FIXNUM_P(rsize)) { stbuf->st_size = FIX2LONG(rsize); } else { stbuf->st_size = 0; } stbuf->st_uid = getuid(); stbuf->st_gid = getgid(); stbuf->st_mtime = init_time; stbuf->st_atime = init_time; stbuf->st_ctime = init_time; return 0; } debug(" nonexistant.\n"); return -ENOENT; } /* rf_readdir * * Used when: 'ls' * * FuseFS will call: 'directory?' on FuseRoot with the given path * as an argument. If the return value is true, then it will in turn * call 'contents' and expects to receive an array of file contents. * * '.' and '..' are automatically added, so the programmer does not * need to worry about those. */ static int rf_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi) { VALUE contents; VALUE cur_entry; VALUE retval; debug("rf_readdir(%s)\n", path ); /* This is what fuse does to turn off 'unused' warnings. */ (void) offset; (void) fi; /* FuseRoot must exist */ if (FuseRoot == Qnil) { if (!strcmp(path,"/")) { filler(buf,".", NULL, 0); filler(buf,"..", NULL, 0); return 0; } return -ENOENT; } if (strcmp(path,"/") != 0) { debug(" Checking is_directory? ..."); retval = rf_call(path, is_directory,Qnil); if (!RTEST(retval)) { debug(" no.\n"); return -ENOENT; } debug(" yes.\n"); } /* These two are Always in a directory */ filler(buf,".", NULL, 0); filler(buf,"..", NULL, 0); retval = rf_call(path, id_dir_contents,Qnil); if (!RTEST(retval)) { return 0; } if (TYPE(retval) != T_ARRAY) { return 0; } /* Duplicate the array, just in case. */ /* TODO: Do this better! */ retval = rb_funcall(retval,id_dup,0); while ((cur_entry = rb_ary_shift(retval)) != Qnil) { if (TYPE(cur_entry) != T_STRING) continue; filler(buf,STR2CSTR(cur_entry),NULL,0); } return 0; } /* rf_mknod * * Used when: This is called when a file is created. * * Note that this is actually almost useless to FuseFS, so all we do is check * if a path is writable? and if so, return true. The open() will do the * actual work of creating the file. */ static int rf_mknod(const char *path, mode_t umode, dev_t rdev) { opened_file *ptr; debug("rf_mknod(%s)\n", path); /* Make sure it's not already open. */ debug(" Checking if it's opened ..."); if (file_openedP(path)) { debug(" yes.\n"); return -EACCES; } debug(" no.\n"); /* We ONLY permit regular files. No blocks, characters, fifos, etc. */ debug(" Checking if an IFREG is requested ..."); if (!S_ISREG(umode)) { debug(" no.\n"); return -EACCES; } debug(" yes.\n"); debug(" Checking if it's an editor file ..."); switch (editor_fileP(path)) { case 2: debug(" yes, and it exists.\n"); return -EEXIST; case 1: debug(" yes, and it doesn't exist.\n"); editor_file *eptr; eptr = ALLOC(editor_file); eptr->writesize = FILE_GROW_SIZE; eptr->value = ALLOC_N(char,eptr->writesize); eptr->path = strdup(path); eptr->size = 0; eptr->raw = 0; eptr->zero_offset = 0; eptr->modified = 0; *(eptr->value) = '\0'; eptr->next = editor_head; editor_head = eptr; return 0; default: debug("no.\n"); } debug(" Checking if it's a file ..." ); if (RTEST(rf_call(path, is_file,Qnil))) { debug(" yes.\n"); return -EEXIST; } debug(" no.\n"); /* Is this writable to */ debug(" Checking if it's writable to ..."); if (!RTEST(rf_call(path,can_write,Qnil))) { debug(" no.\n"); debug(" Checking if it looks like an editor tempfile..."); if (editor_head && (which_editor == EDITOR_VIM)) { char *ptr = strrchr(path,'/'); while (ptr && isdigit(*ptr)) ptr++; if (ptr && (*ptr == '\0')) { debug(" yes.\n"); editor_file *eptr; eptr = ALLOC(editor_file); eptr->writesize = FILE_GROW_SIZE; eptr->value = ALLOC_N(char,eptr->writesize); eptr->path = strdup(path); eptr->raw = 0; eptr->size = 0; eptr->zero_offset = 0; eptr->modified = 0; *(eptr->value) = '\0'; eptr->next = editor_head; editor_head = eptr; return 0; } } debug(" no.\n"); return -EACCES; } debug(" yes.\n"); if (created_file) free(created_file); created_file = strdup(path); return 0; } /* rf_open * * Used when: A file is opened for read or write. * * If called to open a file for reading, then FuseFS will call "read_file" on * FuseRoot, and store the results into the linked list of "opened_file" * structures, so as to provide the same file for mmap, all excutes of * read(), and preventing more than one call to FuseRoot. * * If called on a file opened for writing, FuseFS will first double check * if the file is writable to by calling "writable?" on FuseRoot, passing * the path. If the return value is a truth value, it will create an entry * into the opened_file list, flagged as for writing. * * If called with any other set of flags, this will return -ENOPERM, since * FuseFS does not (currently) need to support anything other than direct * read and write. */ static int rf_open(const char *path, struct fuse_file_info *fi) { VALUE body; char *value; size_t len; char open_opts[4], *optr; opened_file *newfile; debug("rf_open(%s)\n", path); /* Make sure it's not already open. */ debug(" Checking if it's already open ..."); if (file_openedP(path)) { debug(" yes.\n"); return -EACCES; } debug(" no.\n"); debug("Checking if an editor file is requested..."); switch (editor_fileP(path)) { case 2: debug(" yes, and it was created.\n"); return 0; case 1: debug(" yes, but it was not created.\n"); return -ENOENT; default: debug(" no.\n"); } optr = open_opts; switch (fi->flags & 3) { case 0: *(optr++) = 'r'; break; case 1: *(optr++) = 'w'; break; case 2: *(optr++) = 'w'; *(optr++) = 'r'; break; default: debug("Opening a file with something other than rd, wr, or rdwr?"); } if (fi->flags & O_APPEND) *(optr++) = 'a'; *(optr) = '\0'; debug(" Checking for a raw_opened file... "); if (RTEST(rf_call(path,id_raw_open,rb_str_new2(open_opts)))) { debug(" yes.\n"); newfile = ALLOC(opened_file); newfile->size = 0; newfile->value = NULL; newfile->writesize = 0; newfile->zero_offset = 0; newfile->modified = 0; newfile->path = strdup(path); newfile->raw = 1; newfile->next = opened_head; opened_head = newfile; return 0; } debug(" no.\n"); debug(" Checking open type ..."); if ((fi->flags & 3) == O_RDONLY) { debug(" RDONLY.\n"); /* Open for read. */ /* Make sure it exists. */ if (!RTEST(rf_call(path,is_file,Qnil))) { return -ENOENT; } body = rf_call(path, id_read_file,Qnil); /* I don't wanna deal with non-strings :D. */ if (TYPE(body) != T_STRING) { return -ENOENT; } /* We have the body, now save it the entire contents to our * opened_file lists. */ newfile = ALLOC(opened_file); value = rb_str2cstr(body,&newfile->size); newfile->value = ALLOC_N(char,(newfile->size)+1); memcpy(newfile->value,value,newfile->size); newfile->value[newfile->size] = '\0'; newfile->writesize = 0; newfile->zero_offset = 0; newfile->modified = 0; newfile->path = strdup(path); newfile->raw = 0; newfile->next = opened_head; opened_head = newfile; return 0; } else if (((fi->flags & 3) == O_RDWR) || (((fi->flags & 3) == O_WRONLY) && (fi->flags & O_APPEND))) { /* Can we write to it? */ debug(" RDWR or Append.\n"); debug(" Checking if created file ..."); if (created_file && (strcmp(created_file,path) == 0)) { debug(" yes.\n"); newfile = ALLOC(opened_file); newfile->writesize = FILE_GROW_SIZE; newfile->value = ALLOC_N(char,newfile->writesize); newfile->path = strdup(path); newfile->size = 0; newfile->raw = 0; newfile->zero_offset = 0; *(newfile->value) = '\0'; newfile->modified = 0; newfile->next = opened_head; opened_head = newfile; return 0; } debug(" no\n"); debug(" Checking if we can write to it..."); if (!RTEST(rf_call(path,can_write,Qnil))) { debug(" yes.\n"); return -EACCES; } debug(" no\n"); /* Make sure it exists. */ if (RTEST(rf_call(path,is_file,Qnil))) { body = rf_call(path, id_read_file,Qnil); /* I don't wanna deal with non-strings :D. */ if (TYPE(body) != T_STRING) { return -ENOENT; } /* We have the body, now save it the entire contents to our * opened_file lists. */ newfile = ALLOC(opened_file); value = rb_str2cstr(body,&newfile->size); newfile->value = ALLOC_N(char,(newfile->size)+1); memcpy(newfile->value,value,newfile->size); newfile->writesize = newfile->size+1; newfile->path = strdup(path); newfile->raw = 0; newfile->zero_offset = 0; } else { newfile = ALLOC(opened_file); newfile->writesize = FILE_GROW_SIZE; newfile->value = ALLOC_N(char,newfile->writesize); newfile->path = strdup(path); newfile->size = 0; newfile->raw = 0; newfile->zero_offset = 0; *(newfile->value) = '\0'; } newfile->modified = 0; if (fi->flags & O_APPEND) { newfile->zero_offset = newfile->size; } newfile->next = opened_head; opened_head = newfile; return 0; } else if ((fi->flags & 3) == O_WRONLY) { debug(" WRONLY.\n"); #ifdef DEBUG if (fi->flags & O_APPEND) debug(" It's opened for O_APPEND\n"); if (fi->flags & O_ASYNC) debug(" It's opened for O_ASYNC\n"); if (fi->flags & O_CREAT) debug(" It's opened for O_CREAT\n"); if (fi->flags & O_EXCL) debug(" It's opened for O_EXCL\n"); if (fi->flags & O_NOCTTY) debug(" It's opened for O_NOCTTY\n"); if (fi->flags & O_NONBLOCK) debug(" It's opened for O_NONBLOCK\n"); if (fi->flags & O_SYNC) debug(" It's opened for O_SYNC\n"); if (fi->flags & O_TRUNC) debug(" It's opened for O_TRUNC\n"); #endif /* Open for write. */ /* Can we write to it? */ debug(" Checking if we can write to it ... "); if (!((created_file && (strcmp(created_file,path) == 0)) || RTEST(rf_call(path,can_write,Qnil)))) { debug(" no.\n"); return -EACCES; } debug(" yes.\n"); /* We can write to it. Create an opened_write_file entry and initialize * it to a small size. */ newfile = ALLOC(opened_file); newfile->writesize = FILE_GROW_SIZE; newfile->value = ALLOC_N(char,newfile->writesize); newfile->path = strdup(path); newfile->size = 0; newfile->zero_offset = 0; newfile->modified = 0; newfile->raw = 0; *(newfile->value) = '\0'; newfile->next = opened_head; opened_head = newfile; if (created_file && (strcasecmp(created_file,path) == 0)) { free(created_file); created_file = NULL; } return 0; } else { debug(" Unknown...\n"); return -ENOENT; } } /* rf_release * * Used when: A file is no longer being read or written to. * * If release is called on a written file, FuseFS will call 'write_to' on * FuseRoot, passing the path and contents of the file. It will then * clear the file information from the in-memory file storage that * FuseFS uses to prevent FuseRoot from receiving incomplete files. * * If called on a file opened for reading, FuseFS will just clear the * in-memory copy of the return value from rf_open. */ static int rf_release(const char *path, struct fuse_file_info *fi) { opened_file *ptr,*prev; int is_editor = 0; debug("rf_release(%s)\n", path); debug(" Checking for opened file ..."); /* Find the opened file. */ for (ptr = opened_head, prev=NULL;ptr;prev = ptr,ptr = ptr->next) if (strcmp(ptr->path,path) == 0) break; /* If we don't have this open, it doesn't exist. */ if (ptr == NULL) { debug(" no.\n"); debug(" Checking for opened editor file ..."); for (ptr = opened_head, prev=NULL;ptr;prev = ptr,ptr = ptr->next) if (strcmp(ptr->path,path) == 0) { is_editor = 1; break; } } if (ptr == NULL) { debug(" no.\n"); return -ENOENT; } debug(" yes.\n"); /* If it's opened for raw read/write, call raw_close */ debug(" Checking if it's opened for raw write..."); if (ptr->raw) { /* raw read */ debug(" yes.\n"); rf_call(path,id_raw_close,Qnil); } else { debug(" no.\n"); /* Is this a file that was open for write? * * If so, call write_to. */ debug(" Checking if it's for write ...\n"); if ((!ptr->raw) && (ptr->writesize != 0) && !editor_fileP(path)) { debug(" yes ..."); if (ptr->modified) { debug(" and modified.\n"); rf_call(path,id_write_to,rb_str_new(ptr->value,ptr->size)); } else { debug(" and not modified.\n"); if (!handle_editor) { debug(" ... But calling write anyawy."); rf_call(path,id_write_to,rb_str_new(ptr->value,ptr->size)); } } } } /* Free the file contents. */ if (!is_editor) { if (prev == NULL) { opened_head = ptr->next; } else { prev->next = ptr->next; } if (ptr->value) free(ptr->value); free(ptr->path); free(ptr); } return 0; } /* rf_touch * * Used when: A program tries to modify the file's times. * * We use this for a neat side-effect thingy. When a file is touched, we * call the "touch" method. i.e: "touch button" would call * "FuseRoot.touch('/button')" and something *can* happen. =). */ static int rf_touch(const char *path, struct utimbuf *ignore) { debug("rf_touch(%s)\n", path); rf_call(path,id_touch,Qnil); return 0; } /* rf_rename * * Used when: a file is renamed. * * When FuseFS receives a rename command, it really just removes the old file * and creates the new file with the same contents. */ static int rf_rename(const char *path, const char *dest) { /* Does it exist to be edited? */ int iseditor = 0; if (editor_fileP(path) == 2) { iseditor = 1; } else { debug("rf_rename(%s,%s)\n", path,dest); debug(" Checking if %s is file ...", path); if (!RTEST(rf_call(path,is_file,Qnil))) { debug(" no.\n"); return -ENOENT; } debug(" yes.\n"); /* Can we remove the old one? */ debug(" Checking if we can delete %s ...", path); if (!RTEST(rf_call(path,can_delete,Qnil))) { debug(" no.\n"); return -EACCES; } debug(" yes.\n"); } /* Can we create the new one? */ debug(" Checking if we can write to %s ...", dest); if (!RTEST(rf_call(dest,can_write,Qnil))) { debug(" no.\n"); return -EACCES; } debug(" yes.\n"); /* Copy it over and then remove. */ debug(" Copying.\n"); if (iseditor) { editor_file *eptr,*prev; for (eptr=editor_head,prev=NULL;eptr;prev = eptr,eptr = eptr->next) { if (strcmp(path,eptr->path) == 0) { if (prev == NULL) { editor_head = eptr->next; } else { prev->next = eptr->next; } VALUE body = rb_str_new(eptr->value,eptr->size); rf_call(dest,id_write_to,body); free(eptr->value); free(eptr->path); free(eptr); break; } } } else { VALUE body = rf_call(path,id_read_file,Qnil); if (TYPE(body) != T_STRING) { /* We just write a null file, then. Ah well. */ VALUE newstr = rb_str_new2(""); rf_call(path,id_delete,Qnil); rf_call(dest,id_write_to,newstr); } else { rf_call(path,id_delete,Qnil); rf_call(dest,id_write_to,body); } } return 0; } /* rf_unlink * * Used when: a file is removed. * * This calls can_remove? and remove() on FuseRoot. */ static int rf_unlink(const char *path) { editor_file *eptr,*prev; debug("rf_unlink(%s)\n",path); debug(" Checking if it's an editor file ..."); switch (editor_fileP(path)) { case 2: debug(" yes. Removing.\n"); for (eptr=editor_head,prev=NULL;eptr;prev = eptr,eptr = eptr->next) { if (strcmp(path,eptr->path) == 0) { if (prev == NULL) { editor_head = eptr->next; } else { prev->next = eptr->next; } free(eptr->value); free(eptr->path); free(eptr); return 0; } } return -ENOENT; case 1: debug(" yes, but it wasn't created.\n"); return -ENOENT; } debug(" no.\n"); /* Does it exist to be removed? */ debug(" Checking if it exists..."); if (!RTEST(rf_call(path,is_file,Qnil))) { debug(" no.\n"); return -ENOENT; } debug(" yes.\n"); /* Can we remove it? */ debug(" Checking if we can remove it..."); if (!RTEST(rf_call(path,can_delete,Qnil))) { debug(" yes.\n"); return -EACCES; } debug(" no.\n"); /* Ok, remove it! */ debug(" Removing it.\n"); rf_call(path,id_delete,Qnil); return 0; } /* rf_truncate * * Used when: a file is truncated. * * If this is an existing file?, that is writable? to, then FuseFS will * read the file, truncate it, and call write_to with the new value. */ static int rf_truncate(const char *path, off_t offset) { debug( "rf_truncate(%s,%d)\n", path, offset ); debug("Checking if it's an editor file ... "); if (editor_fileP(path)) { debug(" Yes.\n"); opened_file *ptr; for (ptr = opened_head;ptr;ptr = ptr->next) { if (!strcmp(ptr->path,path)) { ptr->size = offset; return 0; } } return 0; } /* Does it exist to be truncated? */ if (!RTEST(rf_call(path,is_file,Qnil))) { return -ENOENT; } /* Can we write to it? */ if (!RTEST(rf_call(path,can_delete,Qnil))) { return -EACCES; } /* If offset is 0, then we just overwrite it with an empty file. */ if (offset > 0) { VALUE newstr = rb_str_new2(""); rf_call(path,id_write_to,newstr); } else { VALUE body = rf_call(path,id_read_file,Qnil); if (TYPE(body) != T_STRING) { /* We just write a null file, then. Ah well. */ VALUE newstr = rb_str_new2(""); rf_call(path,id_write_to,newstr); } else { long size; char *str = rb_str2cstr(body,&size); /* Just in case offset is bigger than the file. */ if (offset >= size) return 0; str[offset] = '\0'; rf_call(path,id_write_to,rb_str_new2(str)); } } return 0; } /* rf_mkdir * * Used when: A user calls 'mkdir' * * This calls can_mkdir? and mkdir() on FuseRoot. */ static int rf_mkdir(const char *path, mode_t mode) { debug("rf_mkdir(%s)",path); /* Does it exist? */ if (RTEST(rf_call(path,is_directory,Qnil))) return -EEXIST; if (RTEST(rf_call(path,is_file,Qnil))) return -EEXIST; /* Can we mkdir it? */ if (!RTEST(rf_call(path,can_mkdir,Qnil))) return -EACCES; /* Ok, mkdir it! */ rf_call(path,id_mkdir,Qnil); return 0; } /* rf_rmdir * * Used when: A user calls 'rmdir' * * This calls can_rmdir? and rmdir() on FuseRoot. */ static int rf_rmdir(const char *path) { debug("rf_rmdir(%s)",path); /* Does it exist? */ if (!RTEST(rf_call(path,is_directory,Qnil))) { if (RTEST(rf_call(path,is_file,Qnil))) { return -ENOTDIR; } else { return -ENOENT; } } /* Can we rmdir it? */ if (!RTEST(rf_call(path,can_rmdir,Qnil))) return -EACCES; /* Ok, rmdir it! */ rf_call(path,id_rmdir,Qnil); return 0; } /* rf_write * * Used when: a file is written to by the user. * * This does not access FuseRoot at all. Instead, it appends the written * data to the opened_file entry, growing its memory usage if necessary. */ static int rf_write(const char *path, const char *buf, size_t size, off_t offset, struct fuse_file_info *fi) { debug("rf_write(%s)",path); opened_file *ptr; debug( " Offset is %d\n", offset ); debug(" Checking if file is open... "); /* Find the opened file. */ for (ptr = opened_head;ptr;ptr = ptr->next) if (strcmp(ptr->path,path) == 0) break; /* If we don't have this open, we can't write to it. */ if (ptr == NULL) { for (ptr = editor_head;ptr;ptr = ptr->next) if (strcmp(ptr->path,path) == 0) break; } if (ptr == NULL) { debug(" no.\n"); return 0; } debug(" yes.\n"); /* Make sure it's open for write ... */ /* If it's opened for raw read/write, call raw_write */ debug(" Checking if it's opened for raw write..."); if (ptr->raw) { /* raw read */ VALUE args = rb_ary_new(); debug(" yes.\n"); rb_ary_push(args,INT2NUM(offset)); rb_ary_push(args,INT2NUM(size)); rb_ary_push(args,rb_str_new(buf,size)); rf_call(path,id_raw_write,args); return size; } debug(" no.\n"); debug(" Checking if it's open for write ..."); if (ptr->writesize == 0) { debug(" no.\n"); return 0; } debug(" yes.\n"); /* Mark it modified. */ ptr->modified = 1; /* We have it, so now we need to write to it. */ offset += ptr->zero_offset; /* Grow memory if necessary. */ if ((offset + size + 1) > ptr->writesize) { size_t newsize; newsize = offset + size + 1 + FILE_GROW_SIZE; newsize -= newsize % FILE_GROW_SIZE; ptr->writesize = newsize; ptr->value = REALLOC_N(ptr->value, char, newsize); } memcpy(ptr->value + offset, buf, size); /* I really don't know if a null bit is required, but this * also functions as a size bit I can pass to rb_string_new2 * to allow binary data */ if (offset+size > ptr->size) ptr->size = offset+size; ptr->value[ptr->size] = '\0'; return size; } /* rf_read * * Used when: A file opened by rf_open is read. * * In most cases, this does not access FuseRoot at all. It merely reads from * the already-read 'file' that is saved in the opened_file list. * * For files opened with raw_open, it calls raw_read */ static int rf_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi) { opened_file *ptr; debug( "rf_read(%s)\n", path ); /* Find the opened file. */ for (ptr = opened_head;ptr;ptr = ptr->next) if (strcmp(ptr->path,path) == 0) break; /* If we don't have this open, it doesn't exist. */ if (ptr == NULL) return -ENOENT; /* If it's opened for raw read/write, call raw_read */ if (ptr->raw) { /* raw read */ VALUE args = rb_ary_new(); rb_ary_push(args,INT2NUM(offset)); rb_ary_push(args,INT2NUM(size)); VALUE ret = rf_call(path,id_raw_read,args); if (!RTEST(ret)) return 0; if (TYPE(ret) != T_STRING) return 0; memcpy(buf, RSTRING_LEN(ret), RSTRING_LEN(ret)); return RSTRING_LEN(ret); } /* Is there anything left to read? */ if (offset < ptr->size) { if (offset + size > ptr->size) size = ptr->size - offset; memcpy(buf, ptr->value + offset, size); return size; } return 0; } static int rf_fsyncdir(const char * path, int p, struct fuse_file_info *fi) { return 0; } static int rf_utime(const char *path, struct utimbuf *ubuf) { return 0; } static int rf_statfs(const char *path, struct statvfs *buf) { return 0; } /* rf_oper * * Used for: FUSE utilizes this to call operations at the appropriate time. * * This is utilized by rf_mount */ static struct fuse_operations rf_oper = { .getattr = rf_getattr, .readdir = rf_readdir, .mknod = rf_mknod, .unlink = rf_unlink, .mkdir = rf_mkdir, .rmdir = rf_rmdir, .truncate = rf_truncate, .rename = rf_rename, .open = rf_open, .release = rf_release, .utime = rf_touch, .read = rf_read, .write = rf_write, .fsyncdir = rf_fsyncdir, .utime = rf_utime, .statfs = rf_statfs, }; /* rf_set_root * * Used by: FuseFS.set_root * * This defines FuseRoot, which is the crux of FuseFS. It is required to * have the methods "directory?" "file?" "contents" "writable?" "read_file" * and "write_to" */ VALUE rf_set_root(VALUE self, VALUE rootval) { if (self != cFuseFS) { rb_raise(cFSException,"Error: 'set_root' called outside of FuseFS?!"); return Qnil; } rb_iv_set(cFuseFS,"@root",rootval); FuseRoot = rootval; return Qtrue; } /* rf_handle_editor * * Used by: FuseFS.handle_editor * * If passed a false value, then FuseFS will not attempt to handle editor * swap files on its own, instead passing them to the filesystem as * normal files. */ VALUE rf_handle_editor(VALUE self, VALUE troo) { if (self != cFuseFS) { rb_raise(cFSException,"Error: 'set_root' called outside of FuseFS?!"); return Qnil; } handle_editor = RTEST(troo); return Qtrue; } /* rf_mount_to * * Used by: FuseFS.mount_to(dir) * * FuseFS.mount_to(dir) calls FUSE to mount FuseFS under the given directory. */ VALUE rf_mount_to(int argc, VALUE *argv, VALUE self) { struct fuse_args *opts; VALUE mountpoint; int i; char *cur; if (self != cFuseFS) { rb_raise(cFSException,"Error: 'mount_to' called outside of FuseFS?!"); return Qnil; } if (argc == 0) { rb_raise(rb_eArgError,"mount_to requires at least 1 argument!"); return Qnil; } mountpoint = argv[0]; Check_Type(mountpoint, T_STRING); opts = ALLOC(struct fuse_args); opts->argc = argc; opts->argv = ALLOC_N(char *, opts->argc); opts->allocated = 1; opts->argv[0] = strdup("-odirect_io"); for (i = 1; i < argc; i++) { cur = StringValuePtr(argv[i]); opts->argv[i] = ALLOC_N(char, RSTRING_LEN(argv[i]) + 2); sprintf(opts->argv[i], "-o%s", cur); } rb_iv_set(cFuseFS,"@mountpoint",mountpoint); fusefs_setup(STR2CSTR(mountpoint), &rf_oper, opts); return Qtrue; } /* rf_fd * * Used by: FuseFS.fuse_fd(dir) * * FuseFS.fuse_fd returns the file descriptor of the open handle on the * /dev/fuse object that is utilized by FUSE. This is crucial for letting * ruby keep control of the script, as it can now use IO.select, rather * than turning control over to fuse_main. */ VALUE rf_fd(VALUE self) { int fd = fusefs_fd(); if (fd < 0) return Qnil; return INT2NUM(fd); } /* rf_process * * Used for: FuseFS.process * * rf_process, which calls fusefs_process, is the other crucial portion to * keeping ruby in control of the script. fusefs_process will read and * process exactly one command from the fuse_fd. If this is called when * there is no incoming data waiting, it *will* hang until it receives a * command on the fuse_fd */ VALUE rf_process(VALUE self) { fusefs_process(); } /* rf_uid and rf_gid * * Used by: FuseFS.reader_uid and FuseFS.reader_gid * * These return the UID and GID of the processes that are causing the * separate Fuse methods to be called. This can be used for permissions * checking, returning a different file for different users, etc. */ VALUE rf_uid(VALUE self) { int fd = fusefs_uid(); if (fd < 0) return Qnil; return INT2NUM(fd); } VALUE rf_gid(VALUE self) { int fd = fusefs_gid(); if (fd < 0) return Qnil; return INT2NUM(fd); } /* Init_fusefs_lib() * * Used by: Ruby, to initialize FuseFS. * * This is just stuff to set up and establish the Ruby module FuseFS and * its methods. */ void Init_fusefs_lib() { opened_head = NULL; init_time = time(NULL); /* module FuseFS */ cFuseFS = rb_define_module("FuseFS"); /* Our exception */ cFSException = rb_define_class_under(cFuseFS,"FuseFSException",rb_eStandardError); /* def Fuse.run */ rb_define_singleton_method(cFuseFS,"fuse_fd", (rbfunc) rf_fd, 0); rb_define_singleton_method(cFuseFS,"reader_uid", (rbfunc) rf_uid, 0); rb_define_singleton_method(cFuseFS,"uid", (rbfunc) rf_uid, 0); rb_define_singleton_method(cFuseFS,"reader_gid", (rbfunc) rf_gid, 0); rb_define_singleton_method(cFuseFS,"gid", (rbfunc) rf_gid, 0); rb_define_singleton_method(cFuseFS,"process", (rbfunc) rf_process, 0); rb_define_singleton_method(cFuseFS,"mount_to", (rbfunc) rf_mount_to, -1); rb_define_singleton_method(cFuseFS,"mount_under", (rbfunc) rf_mount_to, -1); rb_define_singleton_method(cFuseFS,"mountpoint", (rbfunc) rf_mount_to, -1); rb_define_singleton_method(cFuseFS,"set_root", (rbfunc) rf_set_root, 1); rb_define_singleton_method(cFuseFS,"root=", (rbfunc) rf_set_root, 1); rb_define_singleton_method(cFuseFS,"handle_editor", (rbfunc) rf_handle_editor, 1); rb_define_singleton_method(cFuseFS,"handle_editor=", (rbfunc) rf_handle_editor, 1); #undef RMETHOD #define RMETHOD(name,cstr) \ name = rb_intern(cstr); RMETHOD(id_dir_contents,"contents"); RMETHOD(id_read_file,"read_file"); RMETHOD(id_write_to,"write_to"); RMETHOD(id_delete,"delete"); RMETHOD(id_mkdir,"mkdir"); RMETHOD(id_rmdir,"rmdir"); RMETHOD(id_touch,"touch"); RMETHOD(id_size,"size"); RMETHOD(is_directory,"directory?"); RMETHOD(is_file,"file?"); RMETHOD(is_executable,"executable?"); RMETHOD(can_write,"can_write?"); RMETHOD(can_delete,"can_delete?"); RMETHOD(can_mkdir,"can_mkdir?"); RMETHOD(can_rmdir,"can_rmdir?"); RMETHOD(id_raw_open,"raw_open"); RMETHOD(id_raw_close,"raw_close"); RMETHOD(id_raw_read,"raw_read"); RMETHOD(id_raw_write,"raw_write"); RMETHOD(id_dup,"dup"); } fusefs-0.7.0/ext/fusefs_fuse.h0000644000175000017500000000062511745335420014772 0ustar paulpaul/* fusefs_fuse.h */ /* This is rewriting most of the things that occur * in fuse_main up through fuse_loop */ #ifndef __FUSEFS_FUSE_H_ #define __FUSEFS_FUSE_H_ struct fuse_args; int fusefs_fd(); int fusefs_unmount(); int fusefs_ehandler(); int fusefs_setup(char *mountpoint, const struct fuse_operations *op, struct fuse_args *opts); int fusefs_process(); int fusefs_uid(); int fusefs_gid(); #endif fusefs-0.7.0/ext/fusefs_fuse.c0000644000175000017500000000614611745335420014771 0ustar paulpaul/* fusefs_fuse.c */ /* This is rewriting most of the things that occur * in fuse_main up through fuse_loop */ #define FUSE_USE_VERSION 26 #define _FILE_OFFSET_BITS 64 #include #include #include #include #include #include #include #include #include #include #include #include struct fuse *fuse_instance = NULL; struct fuse_chan *fusech = NULL; static char *mounted_at = NULL; static int set_one_signal_handler(int signal, void (*handler)(int)); int fusefs_fd() { if(fusech == NULL) return -1; return fuse_chan_fd(fusech); } int fusefs_unmount() { char buf[128]; if (mounted_at && fusech) { fuse_unmount(mounted_at, fusech); sprintf(buf, "/sbin/umount %s", mounted_at); system(buf); } if (fuse_instance) fuse_destroy(fuse_instance); fuse_instance = NULL; free(mounted_at); fusech = NULL; } static void fusefs_ehandler() { if (fuse_instance != NULL) { fusefs_unmount(); } } int fusefs_setup(char *mountpoint, const struct fuse_operations *op, struct fuse_args *opts) { fusech = NULL; if (fuse_instance != NULL) { return 0; } if (mounted_at != NULL) { return 0; } /* First, mount us */ fusech = fuse_mount(mountpoint, opts); if (fusech == NULL) return 0; fuse_instance = fuse_new(fusech, opts, op, sizeof(*op), NULL); if (fuse_instance == NULL) goto err_unmount; /* Set signal handlers */ if (set_one_signal_handler(SIGHUP, fusefs_ehandler) == -1 || set_one_signal_handler(SIGINT, fusefs_ehandler) == -1 || set_one_signal_handler(SIGTERM, fusefs_ehandler) == -1 || set_one_signal_handler(SIGPIPE, SIG_IGN) == -1) return 0; atexit(fusefs_ehandler); /* We've initialized it! */ mounted_at = strdup(mountpoint); return 1; err_destroy: fuse_destroy(fuse_instance); err_unmount: fuse_unmount(mountpoint, fusech); return 0; } int fusefs_uid() { struct fuse_context *context = fuse_get_context(); if (context) return context->uid; return -1; } int fusefs_gid() { struct fuse_context *context = fuse_get_context(); if (context) return context->gid; return -1; } int fusefs_process() { /* This gets exactly 1 command out of fuse fd. */ /* Ideally, this is triggered after a select() returns */ if (fuse_instance != NULL) { struct fuse_cmd *cmd; if (fuse_exited(fuse_instance)) return 0; cmd = fuse_read_cmd(fuse_instance); if (cmd == NULL) return 1; fuse_process_cmd(fuse_instance, cmd); } return 1; } static int set_one_signal_handler(int signal, void (*handler)(int)) { struct sigaction sa; struct sigaction old_sa; memset(&sa, 0, sizeof(struct sigaction)); sa.sa_handler = handler; sigemptyset(&(sa.sa_mask)); sa.sa_flags = 0; if (sigaction(signal, NULL, &old_sa) == -1) { perror("FUSE: cannot get old signal handler"); return -1; } if (old_sa.sa_handler == SIG_DFL && sigaction(signal, &sa, NULL) == -1) { perror("Cannot set signal handler"); return -1; } return 0; } fusefs-0.7.0/ext/extconf.rb0000644000175000017500000000025611745335420014277 0ustar paulpaulrequire 'mkmf' dir_config('fusefs_lib.so') if have_library('fuse_ino64') || have_library('fuse') create_makefile('fusefs_lib') else puts "No FUSE install available" end fusefs-0.7.0/ext/MANIFEST0000644000175000017500000000011011745335420013422 0ustar paulpaulfusefs_fuse.c fusefs_fuse.h fusefs_lib.c fusefs-0.7.0/VERSION0000644000175000017500000000000611745335420012545 0ustar paulpaul0.7.0 fusefs-0.7.0/TODO0000644000175000017500000000057011745335420012173 0ustar paulpaulTODO for FuseFS =============== - Problem with editors: vim attempts to create and then delete a numbered file. This can be an issue if the filename (all digits) is reported as not valid. I am looking for a solution for this, but it's gonna be a pain. - Tests, more tests, and unit tests! - Consider FIFOs? We can use FIFOs to mimic TCP internet, if we do this right. fusefs-0.7.0/Rakefile0000644000175000017500000000240711745335420013151 0ustar paulpaulrequire 'rubygems' require 'rake' begin require 'jeweler' Jeweler::Tasks.new do |gem| gem.name = "fusefs" gem.summary = %Q{fusefs} gem.description = %Q{Gemified} gem.email = "shane@duairc.com" gem.homepage = "http://github.com/duairc/fusefs" gem.authors = ["Shane"] gem.extensions = ["ext/extconf.rb"] end Jeweler::GemcutterTasks.new rescue LoadError puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler" end require 'rake/testtask' Rake::TestTask.new(:test) do |test| test.libs << 'lib' << 'test' test.pattern = 'test/**/*_test.rb' test.verbose = true end begin require 'rcov/rcovtask' Rcov::RcovTask.new do |test| test.libs << 'test' test.pattern = 'test/**/*_test.rb' test.verbose = true end rescue LoadError task :rcov do abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov" end end task :test => :check_dependencies task :default => :test require 'rake/rdoctask' Rake::RDocTask.new do |rdoc| if File.exist?('VERSION') version = File.read('VERSION') else version = "" end rdoc.rdoc_dir = 'rdoc' rdoc.title = "fusefs #{version}" rdoc.rdoc_files.include('README*') rdoc.rdoc_files.include('lib/**/*.rb') end fusefs-0.7.0/README0000644000175000017500000000167611745335420012373 0ustar paulpaulFuseFS README ============ FuseFS is a library aimed at allowing Ruby programmers to quickly and easily create virtual filesystems with little more than a few lines of code. A "hello world" file system equivalent to the one demonstrated on fuse.sourceforge.org is just 20 lines of code! FuseFS is *NOT* a full implementation of the FUSE api. rfuse is designed for that. Requirements ------------ * FUSE (http://fuse.sourceforge.org) * Ruby (>= 1.8) (* C compiler) Install ------- gem install fusefs Usage ----- Some sample ruby filesystems are listed in "sample/" When you run a fusefs script, it will listen on a socket indefinitely, so either background the script or open another terminal to mosey around in the filesystem. Also, check the API.txt file for more use. License ------- MIT license, in file "LICENSE" Author: Greg Millam . Port/Maintainer: Shane fusefs-0.7.0/LICENSE0000644000175000017500000000210111745335420012500 0ustar paulpaulCopyright (c) 2005 Greg Millam. Copyright (c) 2009 Kyle Maxwell. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. fusefs-0.7.0/Changes.txt0000644000175000017500000000357511745335420013624 0ustar paulpaulFuseFS 0.7 ========== * It's now a Rubygem on Github FuseFS 0.6 ========== * FuseFS.mount_under() now takes FUSE options as optional arguments, such as 'allow_other' and 'allow_root' * rmdir now works. (Whoops!) FuseFS 0.5.1 ============ * Bugfix for dealing with raw files (Thanks, Kent Sibilev) FuseFS 0.5 ========== * Fixed for FUSE 2.4. direct_io turned from a mount option in 2.3 to a lib option in 2.4. * _why_the_lucky_stiff's railsfs.rb added to the samples/ dir. * FuseRoot#raw_open is called with the path and "r" "w" "rw" for read or write modes, along with "a" if it is called for appending. * If raw_open returns true, FuseFS will call raw_read, raw_write, and raw_close at necessary points. (See API.txt) * FuseRoot#size is optionally called to determine file sizes, should the user want a file size to be reported as anything other than 0. FuseFS 0.4 ========== * Stronger and more robust handling of editor swap files, but still incomplete. * Peppered with debug statements. * A bit cleaner method of calling ruby functions. * rf_rename fixed. Whoops! FuseFS 0.3 ========== * read_file borked FuseFS when a binary file was returned. Instead of using strdup, it now mallocs according to the returned size, as appropriate. * Addition of sample/openurifs.rb * 'touch file' emptied a file, since it opened and then released without writing. I added a 'modified' flag to fix this. * 'touch' method call added, and called when a program attempts to modify a file's time. * 'executable?' check added in case programmer wants to the file to report itself as executable to the filesystem. * vim and emacs swap files are not passed to FuseFS =). FuseFS 0.2 ========== * Fix call for deleting files from 'remove' to 'delete' to match API spec. * Addition of sample/yamlfs.rb FuseFS 0.1 ========== Initial import. fusefs-0.7.0/API.txt0000644000175000017500000002134711745335420012662 0ustar paulpaulFuseFS API DOCUMENT =================== Last updated: 2005.09.19 by Greg Millam WARNING ------- Note: If you use DirLink (in demo.rb) or in any way access a FuseFS filesystem from *within* the ruby script accessing the FuseFS, then FuseFS will hang, and the only recourse is a kill -KILL. Also: If there are any open files or shells with 'pwd's in your filesystem when you exit your ruby script, fuse *might* not actually be unmounted. To unmount a path yourself, run the command: fusermount -u to unmount any FUSE filesystems mounted at . FuseFS API ---------- FuseFS provides a layer of abstraction to a programmer who wants to create a virtual filesystem via FUSE. FuseFS programs consist of two parts: 1) FuseFS, which is defined in 'fusefs.rb' 2) An object that defines a virtual directory. This must define a number of methods (given below, in "Directory Methods" section) in order to be usable. To write a FuseFS program, you must: * Define and create a Directory object that responds to the methods required by FuseFS for its desired use. * Call FuseFS.set_root with the object defining your virtual directory. * Mount FuseFS under a real directory on your filesystem. * Call FuseFS.run to start receiving and executing events. Happy Filesystem Hacking! Hello World FS -------------- helloworld.rb This creates a filesystem that contains exactly 1 file: "hello.txt" that, when read, returns "Hello, World!" This is not writable to, and contains no other files. require 'fusefs' class HelloDir def contents(path) ['hello.txt'] end def file?(path) path -- '/hello.txt' end def read_file(path) "Hello, World!\n" end end hellodir = HelloDir.new FuseFS.set_root( hellodir ) # Mount under a directory given on the command line. FuseFS.mount_under ARGV.shift FuseFS.run Directory Methods ----------------- Without any methods defined, any object is by default a content-less, file-less directory. The following are necessary for most or all filesystems: Directory listing and file type methods: :contents(path) # Return an array of file and dirnames within . :directory?(path) # Return true if is a directory. :file?(path) # Return true if is a file (not a directory). :executable?(path) # Return true if is an executable file. :size(path) # Return the file size. Necessary for apache, xmms, etc. File reading: :read_file(path) # Return the contents of the file at location . The following are only necessary if you want a filesystem that can be modified by the user. Without defining any of the below, the contents of the filesystem are automatically read-only. File manipulation: :can_write?(path) # Return true if the user can write to file at . :write_to(path,str) # Write the contents of to file at . :can_delete?(path) # Return true if the user can delete file at . :delete(path) # Delete the file at Directory manipulation: :can_mkdir?(path) # Return true if user can make a directory at . :mkdir(path) # Make a directory at path. :can_rmdir?(path) # Return true if user can remove directory at . :rmdir(path) # Remove it. Neat "toy": :touch(path) # Called when a file is 'touch'd or otherwise has their timestamps explicitly modified. I envision this as a neat toy, maybe you can use it for a push-button file? "touch button" -> unmounts fusefs? "touch musicfile.mp3" -> Play the mp3. If you want a lower level control of your file, then you can use: :raw_open(path,mode) # mode is "r" "w" or "rw", with "a" if the file is opened for append. If raw_open returns true, then the following calls are made: :raw_read(path,off,sz) # Read sz bites from file at path starting at offset off :raw_write(path,off,sz,buf) # Write sz bites of buf to path starting at offset off :raw_close(path) # Close the file. Method call flow ================ List contents: :directory? will be checked before :contents (Most 'ls' or 'dir' functions will go on next to getattr() for all contents) Read file: :file? will be checked before :read_file Getattr :directory? will be checked first. :file? will be checked before :can_write? :file? will be checked before :executable? Writing files: * directory? is usually called on the directory The FS wants to write a new file to, before this can occur. :can_write? will be checked before :write_to Deleting files: :file? will be checked before :can_delete? :can_delete? will be checked before :delete Creating dirs: * directory? is usually called on the directory The FS wants to make a new directory in, before this can occur. :directory? will be checked. :can_mkdir? is called only if :directory? is false. :can_mkdir? will be checked before :mkdir Deleting dirs: :directory? will be checked before :can_rmdir? :can_rmdir? will be checked before :rmdir module FuseFS ------------- FuseFS methods: FuseFS.set_root(object) Set the root virtual directory to . All queries for obtaining file information is directed at object. FuseFS.mount_under(path[,opt[,opt,...]]) This will cause FuseFS to virtually mount itself under the given path. 'path' is required to be a valid directory in your actual filesystem. 'opt's are FUSE options. Most likely, you will only want 'allow_other' or 'allow_root'. The two are mutually exclusive in FUSE, but allow_other will let other users, including root, access your filesystem. allow_root will only allow root to access it. Also available for FuseFS users are: default_permissions, max_read=N, fsname=NAME. For more information, look at FUSE. (P.S: I know FUSE allows other options, but I don't think any of the rest will do any good with FuseFS. If you think otherwise, please let me know!) FuseFS.run This is the final step to make your virtual filesystem accessible. It is recommended you run this as your main thread, but you can thread off to run this. FuseFS.handle_editor = bool (true by default) If handle_editor is true, then FuseFS will attempt to capture all editor files and prevent them from being passed to FuseRoot. It also prevents created and unmodified files from being passed as well, as vim (among others) will attempt to create and then remove a file that does not exist. FuseFS.reader_uid and FuseFS.reader_gid When the filesystem is accessed, the accessor's uid or gid is returned by FuseFS.reader_uid and FuseFS.reader_gid. You can use this in determining your permissions, or even provide different files for different users! FuseFS.fuse_fd and FuseFS.process These are not intended for use by the programmer. If you want to muck with this, read the code to see what they do :D. FuseDir ---------- FuseFS::FuseDir defines the methods "split_path" and "scan_path". You should typically inherit from FuseDir in your own directory objects. base, rest = split_path(path) # base is the file or directory in the current context. rest is either nil, or a path that is requested. If 'rest' exists, then you should recurse the paths. base, *rest = scan_path(path) # scan_path returns an array of all directory and file elements given by . This is useful when you're encapsulating an entire fs into one object. MetaDir ------- MetaDir is a full filesystem defined with hashes. It is writable, and the user can create and edit files within it, as well as the programmer. Usage: root = MetaDir.new root.mkdir("/hello") root.write_to("/hello/world","Hello, World!\n") root.write_to("/hello/everybody","Hello, Everybody!\n") FuseFS.set_root(root) Because MetaDir is fully recursive, you can mount your own or other defined directory structures under it. For example, to mount a dictionary filesystem (as demonstrated in samples/dictfs.rb), use: root.mkdir("/dict",DictFS.new) Conclusion ---------- Happy Hacking! If you do anything neat with this, please let me know! My email address is walker@deafcode.com Thanks for using FuseFS! fusefs-0.7.0/.gitignore0000644000175000017500000000010711745335420013467 0ustar paulpaul*.sw? .DS_Store coverage rdoc pkg *.o *.bundle *.log Makefile pid hellofusefs-0.7.0/.document0000644000175000017500000000007411745335420013321 0ustar paulpaulREADME.rdoc lib/**/*.rb bin/* features/**/*.feature LICENSE