require "etc" require "open3" class Pandler::Chroot attr_reader :base_dir, :root_dir, :yumrepo, :mounts def initialize(args = {}) @base_dir = args[:base_dir] || File.expand_path("pandler") @root_dir = args[:root_dir] || File.join(base_dir, "root") @yumrepo = args[:yumrepo] || "file://" + File.join(base_dir, "yumrepo") @mounts = [ { :type => 'proc', :path => '/proc' }, { :type => 'sysfs', :path => '/sys' }, { :type => 'tmpfs', :path => '/dev/shm' }, { :type => 'devpts', :path => '/dev/pts', :options => "gid=#{Etc.getgrnam("tty").gid},mode=0620,ptmxmode=0666,newinstance" }, ] end def real_path(path) File.join(root_dir, path) end def init setup_dirs setup_files setup_devs mount_all true end def install(*pkgs) yum_install(*pkgs) rpm_erase_exclude(*pkgs) end def installed_pkgs rpm_qa end def execute(*cmd) chroot_run_cmd(*cmd) end def clean umount_all FileUtils.remove_entry_secure root_dir end def list rpm_qa end private def yum_install(*pkgs) cmd = ["yum", "--installroot", root_dir, "install"] + pkgs run_cmd(*cmd) end def rpm_erase_exclude(*pkgs) remove_pkgs = gratuitous_pkgs(*pkgs) if remove_pkgs.size > 0 cmd = ["rpm", "--root", root_dir, "--erase"] + remove_pkgs run_cmd(*cmd) end end def gratuitous_pkgs(*pkgs) rpm_qa - pkgs end def rpm_qa run_cmd_capture_stdout("rpm", "--root", root_dir, "-qa").sort end def run_cmd(*cmd) ret = Kernel.system(*cmd) raise "command failed(ret: #{ret}) '#{cmd.join(" ")}'" unless ret end def chroot_run_cmd(*cmd) run_cmd("chroot", root_dir, *cmd) end def run_cmd_capture_stdout(*cmd) Open3.popen3(*cmd) do |stdin, stdout, stderr| stdin.close stdout.readlines.map { |l| l.chomp } end end def write_file(path, content) open(real_path(path), "w") { |f| f.write content } end def mount_all @mounts.each do |entry| mount(entry) end end def mount(entry) unless mounted?(entry) cmd = ["mount", "-t", entry[:type]] cmd.concat ["-o", entry[:options]] if entry.has_key?(:options) cmd.concat ["pandler_mount", real_path(entry[:path])] run_cmd(*cmd) end end def mounted?(entry) `mount`.split("\n").map { |line| line.split[2] }.include?(real_path(entry[:path])) end def umount_all @mounts.each do |entry| umount(entry) end end def umount(entry) if mounted?(entry) cmd = ["umount", "-l", real_path(entry[:path])] run_cmd(*cmd) end end def setup_dirs FileUtils.mkdir_p root_dir dirs = [ '/var/lib/rpm', '/var/lib/yum', '/var/lib/dbus', '/var/log', '/var/lock/rpm', '/var/cache/yum', '/etc/rpm', '/tmp', '/tmp/ccache', '/var/tmp', '/etc/yum.repos.d', '/etc/yum', '/proc', '/sys', ] dirs.each do |dir| FileUtils.mkdir_p real_path(dir) end end def setup_files files = [ '/etc/mtab', '/etc/fstab', '/var/log/yum.log', ] files.each do |file| FileUtils.touch real_path(file) end write_file("/etc/yum/yum.conf", yum_conf) FileUtils.ln_s("yum/yum.conf", real_path("/etc/yum.conf"), { :force => true }) FileUtils.cp("/etc/resolv.conf", real_path("/etc/resolv.conf")) FileUtils.cp("/etc/hosts", real_path("/etc/hosts")) end def setup_devs FileUtils.mkdir_p real_path("/dev/pts") FileUtils.mkdir_p real_path("/dev/shm") python_code = <<-PYTHON import sys, os, os.path, stat devFiles = [ (stat.S_IFCHR | 0666, os.makedev(1, 3), "#{real_path("/dev/null")}"), (stat.S_IFCHR | 0666, os.makedev(1, 7), "#{real_path("/dev/full")}"), (stat.S_IFCHR | 0666, os.makedev(1, 5), "#{real_path("/dev/zero")}"), (stat.S_IFCHR | 0666, os.makedev(1, 8), "#{real_path("/dev/random")}"), (stat.S_IFCHR | 0444, os.makedev(1, 9), "#{real_path("/dev/urandom")}"), (stat.S_IFCHR | 0666, os.makedev(5, 0), "#{real_path("/dev/tty")}"), (stat.S_IFCHR | 0600, os.makedev(5, 1), "#{real_path("/dev/console")}"), # (stat.S_IFCHR | 0666, os.makedev(5, 2), "#{real_path("/dev/ptmx")}"), ] for i in devFiles: if os.path.exists(i[2]): continue else: os.mknod(i[2], i[0], i[1]) sys.exit(0) PYTHON run_cmd("python", "-c", python_code) FileUtils.symlink("/proc/self/fd/0", real_path("/dev/stdin"), { :force => true }) FileUtils.symlink("/proc/self/fd/1", real_path("/dev/stdout"), { :force => true }) FileUtils.symlink("/proc/self/fd/2", real_path("/dev/stderr"), { :force => true }) FileUtils.chown(Etc.getpwnam("root").uid, Etc.getgrnam("tty").gid, real_path("/dev/tty")) # FileUtils.chown(Etc.getpwnam("root").uid, Etc.getgrnam("tty").gid, real_path("/dev/ptmx")) unless File.exists? real_path("/dev/fd") FileUtils.symlink("/proc/self/fd", real_path("/dev/fd"), { :force => true }) end FileUtils.symlink("pts/ptmx", real_path("/dev/ptmx"), { :force => true }) end def yum_conf content = <<-EOF [main] cachedir=/var/cache/yum debuglevel=1 reposdir=/dev/null logfile=/var/log/yum.log retries=20 obsoletes=1 gpgcheck=0 assumeyes=1 syslog_ident=pandler syslog_device= plugins=0 [pandler] name=Pandler enabled=1 baseurl=#{yumrepo} EOF content end end