#!/usr/bin/ruby

# Purpose:
# Allows to remap specified fs-tree to different place and let it
# be accessed & modified by specified user (uid)
#
# Example:
# Extremely handy to let others modify cgroup filesystem which can't
# be mounted under different uid...
# 
# cd /dev
# mkdir cgroup cgroup.wfw
# mount cgroup cgroup -t cgroup -o devices
# ./fuse-rights-proxy.rb
# (elsewhere:) setuidgid wejn sh -c 'echo $$ > /dev/cgroup.wfw/tasks'
#
# Author: Wejn (wejn at box dot cz)
# License: MIT

require 'fusefs'

class FUSERightsProxy
	# constructor {{{
	def initialize(target, uid)
		unless FileTest.directory?(target)
			raise ArgumentError, "target: #{target} doesn't exist"
		end

		if uid.to_i.zero?
			raise ArgumentError, "uid should be non-root"
		end

		@target, @uid = target, uid.to_i
	end
	# }}}

	# dirlist {{{

	def contents(path)
		out = nil
		Dir.chdir(File.join(@target, path)) do
			out = Dir['*']
		end
		out
	end

	# }}}

	# tests {{{
	
	def directory?(path)
		FileTest.directory?(File.join(@target, path))
	end

	def file?(path)
		FileTest.file?(File.join(@target, path))
	end

	# }}}

	# file read {{{

	def read_file(path)
		File.open(File.join(@target, path)).read
	rescue Object
		nil
	end

	# }}}

	# file write {{{

	def can_write?(path)
		(FuseFS.uid.zero? || FuseFS.uid == @uid) && !directory?(path)
	end

	def write_to(path, str)
		File.open(File.join(@target, path), 'w') { |f| f.write(str) }
		true
	rescue Object
		nil
	end

	# }}}

	# file deletion {{{

	def can_delete?(path)
		# also needed for open(path, 'w')
		(FuseFS.uid.zero? || FuseFS.uid == @uid) && file?(path)
	end

	def delete(path)
		file?(path) && File.unlink(File.join(@target, path))
	rescue Object
		nil
	end

	# }}}

	# dir manipulation {{{

	def can_mkdir?(path)
		(FuseFS.uid.zero? || FuseFS.uid == @uid) && !directory?(path) && !file?(path)
	end

	def mkdir(path)
		Dir.mkdir(File.join(@target, path))
		true
	rescue Object
		nil
	end

	def can_rmdir?(path)
		(FuseFS.uid.zero? || FuseFS.uid == @uid) && directory?(path)
	end

	def rmdir(path)
		Dir.rmdir(File.join(@target, path))
		true
	rescue Object
		nil
	end

	# }}}
end

if __FILE__ == $0
	target = '/dev/cgroup'
	mpoint = '/dev/cgroup.wfw'
	uid = 1000

	unless FileTest.directory?(mpoint)
		STDERR.puts "mount point: #{mpoint} doesn't exist"
		exit 1
	end

	unless Process.uid.zero?
		STDERR.puts "must be run as root"
		exit 1
	end

	if File.open('/proc/filesystems').grep(/\sfuse/).empty?
		STDERR.puts "/proc/filesystems indicates lack of FUSE support;" +
			" modprobe it perhaps?"
		exit 1
	end

	unless File.open('/proc/mounts').grep(Regexp.new(mpoint)).empty?
		STDERR.puts "/proc/mounts indicates #{mpoint} is already mounted"
		exit 1
	end

	d = FUSERightsProxy.new(target, uid)
	FuseFS.set_root(d)
	FuseFS.mount_to(mpoint, "allow_other")
	begin
		FuseFS.run
	rescue Interrupt
	end
end

