[devel] MySync

Kirill A. Shutemov =?iso-8859-1?q?kirya85_=CE=C1_mail=2Eru?=
Чт Мар 18 09:21:48 MSK 2004


Тут для себя написал скриптик. Может кому будет интересно...

Что-то типа оффлайного rsync'а -- позволяет побновлять одно дерево файлов
до другого. Предпологается возможность перетаскивать обновления Сизифа 
(скажем, с работы домой), пемерно следующей последовательностью команд:

Дома: ./mysync -s каталог_с_сизифом -o Sisyphus.shot
	Это довольно долгий процесс - у меня на машине(Cel-300A, 320Mb)
	~20 минут. Далее отсылаем полученый файл(в bzip'е ~400kb) на
	работу

На работе: ./mysync -c каталог_с_сизифом Sisyphus.shot -o Sisyphus.tar
	Залеваем Sisypus.tar на компакт или иной носитель, несем домой

Дома: ./mysync -u -r  Sisyphus.tar каталог_с_сизифом
	Вуаля... :)

За подробностями смотрите --help.

Если кого-нить эта поделка заинтересовала могу положить в rpm.

P.S. Требует наличие в системе ruby, tar, diff и md5sum. 

P.P.S. Замена последнего на рубёвый(см. закоменченый код) вызвала увеличение 
времени выполнения первой команды в 2 раза и при этом запросто съедалась вся 
ОП + кусок свопа. Может кто объяснит почему это проиходит?

-- 
Kirill A. Shutemov
E-mail: kirya85 на mail.ru
JID: kas на altlinux.org
ICQ: 152302675

Предыдущее (-alt3) и эти изменения исправляют ошибки, которые, насколько я
понимаю, благодаря технологии PrivilegeSeparation в худшем случае могут
привести к возможности исполнения кода с правами псевдопользователя в
пустом чруте.  К тому же что ещё и не известно, могут ли.
		-- ldv in devel@
----------- следующая часть -----------
#!/usr/bin/env ruby

require 'find'
require 'ftools'
#require 'md5'
require 'tempfile'


$verbose = false
$remove = false
$exit_on_warning = false
$exit_on_error = true

def version 
    "0.1.0"
end

def fatal(msg)
    STDERR.puts("FATAL!: " + msg)
    exit(2)
end

def error(msg)
    STDERR.puts("ERROR!: " + msg)
    exit(1) if $exit_on_error
end

def warning(msg)
    STDERR.puts("WARNING!: " + msg)
    exit(1) if $exit_on_warning
end

def snapshot(path, output=STDOUT)
    STDERR.puts "Generation snapshot for #{path}..." if $verbose	
    
    fatal("#{path} isn't directory") unless FileTest.directory?(path)
    
    output.puts("# MySync snapshot version #{version}", "# Date: #{Time.now}")
    
    arr = []
    Find.find(path){|x|
	arr.push(x)
    }
    arr.sort!
    arr.shift
    
    arr.each{|s|
	x = s.sub(/^#{path}\/?/, '')
	case File.lstat(s).ftype	    
	when "directory"
	    STDERR.puts x if $verbose	
	    output.puts("D #{x}")
	when "file"
#	    f = File.open(s)
#	    output.puts("F #{x} #{MD5.md5(f.readlines.to_s)}")
#	    f.close
	    output.puts("F #{x} " + `md5sum #{s}`.sub(/ [^ ]+$/,''))
	when "link"
	    output.puts("L #{x} #{File.readlink(s)}")
	else
	    warning("Not supported file type: #{s} - #{File.lstat(s).ftype}.")
	end
	output.flush
    }
    STDERR.puts "Generation snapshot for #{path} done." if $verbose	
    
    return output
end

def diff(to, from, output=STDOUT)
    STDERR.puts "Generation diff between #{from} and #{to}..." if $verbose	
    
    fatal("#{from} - no such file or directory.") unless FileTest.exist?(from)
    
    fatal("#{to} - no such file or directory.") unless FileTest.exist?(to)
    
    output.puts("# MySync Diff version #{version}", "# Date: #{Time.now}")
    
    if File.open(from).stat.directory?
	tf = Tempfile.new("from")
	snapshot(from, tf)
	from = tf.path
    end
    
    if File.open(to).stat.directory?
	tf = Tempfile.new("to")
	snapshot(to, tf)
	to = tf.path
    end
    
    arr = `diff -U 0 #{from} #{to}`.split(/\n/)
    arr.delete_if{|s| not s =~ /^[+-][FDL]/}
    
    new = []
    remove = []
    change = []
    
    arr.each{|s|
	path = s.gsub(/[+-][FDL] ([^ ]+).*/, '\1')
	
	if s=~/^\+/
	    new.push(path)
	else
	    remove.push(s.gsub(/[+-][FDL] ([^ ]+).*/, '\1'))
	end
    }
    
    remove.each{|s| change.push(s) if new.include?(s)}
    change.each{|s|
	new.delete(s)
	remove.delete(s)
    }
    change.each{|s| output.puts("C #{s}")}
    new.each{|s| output.puts("N #{s}")}
    remove.each{|s| output.puts("R #{s}")}
    
    output.flush	
    STDERR.puts "Generation diff between #{from} and #{to} done" if $verbose	
    
    return output
end

def create(to, from, output=STDOUT)
    STDERR.puts "Generation tar with changes between #{from} and #{to}" if $verbose	
    
    fatal("#{from} - no such file or directory.") unless FileTest.exist?(from)
    
    fatal("#{to} isn't directory.") unless FileTest.directory?(to)
    
    
    to = File.expand_path(to)
    
    tf = Tempfile.new(".mysync")
    
    diff(to, from, tf)
    
    arr = File.open(tf.path).readlines	
    
    tar = ""
    arr.each{|s|
	tar += " " + s.sub(/^[NC] (.*)\n$/, '\1') if s =~ /^[NC] /
    }
    
    STDERR.puts "Packing tar #{from}" if $verbose	
    output.puts(`tar --no-recursion -c -C #{tf.path.gsub(/\/([^\/]+$)/,' \1')} -C #{to} #{tar} \
#{ $verbose ? '--verbose' : ''}`)
    output.flush
    
    STDERR.puts "Generation tar with changes between #{from} and #{to} done" if $verbose	
    
    return output
end

def update(from, to)
    STDERR.puts "Updating #{to} from #{from}" if $verbose	
    
    fatal("#{to} isn't directory.") unless FileTest.directory?(to)
    
    from = File.expand_path(from)
    
    if FileTest.directory?(from)
	tf = Tempfile.new("tar")
	create(from, to, tf)
	from = tf.path
    end
    
    arr = `tar -O -xf #{from} ".mysync*"`.split("\n")

    pwd = Dir.pwd
    Dir.chdir(to)
    
    remove = []
    
    arr.each{|s|
	a = /^([NCR]) (.*)$/.match(s).to_a
	
	case a[1]
	when "N"
	    error("File #{a[2]} labeled as new, but exist in the dir.") if
		FileTest.exist?(a[2])
	when "C"
	    warning("File #{a[2]} labeled as changed, but not exist in the dir.") if
		!FileTest.exist?(a[2])
	when "R"
	    warning("File #{a[2]} labeled as removable, but not exist in the dir.") if
		!FileTest.exist?(a[2])
	    remove.push(a[2])
	end
    }
    
    STDERR.puts "Unpacking tar #{from}" if $verbose	
    
    error("Untaring files failure.") unless 
	system("tar -xf #{from} #{ $verbose ? '--verbose' : ''}")
    
    remove.reverse_each{|file| File.rm_f(file) or Dir.rmdir(file)} if $remove
    Dir[".mysync*"].each{|file| File.rm_f(file)}
    Dir.chdir(pwd)
    
    STDERR.puts "Updating #{to} from #{from} done" if $verbose	
end

if __FILE__ == $0
    require 'getoptlong'

    def show_usage
	puts "Usage: mysync -s [-o file] [-v] DIR"
	puts "  or   mysync -d [-o file] [-v] (NEWDIR|SNAPSHOT) (ORIGDIR|SNAPSHOT)"
	puts "  or   mysync -c [-o file] [-v] (NEWDIR) (ORIGDIR|SNAPSHOT)"
	puts "  or   mysync -u [-v] [-r] (SRCDIR|TAR) DSTDIR"
	puts "  or   mysync -h"
	puts
	puts "Options"
	puts " -s, --snapshot\t\tCreate snapshot of DIR"
	puts " -d, --diff\t\tCompare two directories or snapshots"
	puts " -c, --create\t\tCreate tar with changes between ORIGDIR or SNAPSHOT and NEWDIR)"
	puts " -u, --update\t\tUpdate DSTDIR to SRCDIR or TAR(created by --create)"
	puts " -r, --remove\t\tRemove files in DSTDIR wich didn't exist in SRCDIR(only with -u)"
	puts " -o, --output=FILE\tWrite output into FILE (not with --update)"
	puts " -v, --verbose\t\tBe verbose(very verbose %), print into stderr"
	puts " -w, --warning-exit\tExit on a WARNING"
	puts " -e, --error-continue\tDon't exit on ERROR"
	puts " -h, --help\t\tShow this usage"
	exit
    end

    opts = GetoptLong.new(
			  [ "--snapshot", "-s", GetoptLong::NO_ARGUMENT],
			  [ "--diff", "-d", GetoptLong::NO_ARGUMENT],
			  [ "--create", "-c", GetoptLong::NO_ARGUMENT],
			  [ "--update", "-u", GetoptLong::NO_ARGUMENT],
			  [ "--remove", "-r", GetoptLong::NO_ARGUMENT],
			  [ "--output", "-o", GetoptLong::REQUIRED_ARGUMENT],
			  [ "--verbose", "-v", GetoptLong::NO_ARGUMENT],
			  [ "--warning-exit", "-w", GetoptLong::NO_ARGUMENT],
			  [ "--error-continue", "-e", GetoptLong::NO_ARGUMENT],
			  [ "--help", "-h", GetoptLong::NO_ARGUMENT]
			  )
    mode = filename = ""

    opts.each{|opt,arg|
	case opt
	when "--help"
	    show_usage
	when "--snapshot"
	    fatal("You only can select one mode.") unless mode == ""
	    mode = "snapshot"
	when "--diff"
	    fatal("You only can select one mode.") unless mode == ""
	    mode = "diff"
	when "--create"
	    fatal("You only can select one mode.") unless mode == ""
	    mode = "create"
	when "--update"
	    fatal("You only can select one mode.") unless mode == ""
	    mode = "update"
	when "--remove"
	    $remove = true
	when "--output"
	    filename = arg
	when "--verbose"
	    $verbose = true
	when "--warning-exit"
	    $exit_on_warning = true
	when "--error-continue"
	    $exit_on_error = false
	end
    }

    error("--output can't be used with --update") if mode == "update" and filename != ""
    error("--remove only can use with --update") if $remove and mode != "update"

    case mode
    when "snapshot"
	show_usage unless ARGV.size == 1
	if filename != ""
	    snapshot(ARGV[0], File.new(filename, File::CREAT|File::TRUNC|File::RDWR))
	else
	    snapshot(ARGV[0])
	end
	exit
    when "diff"
	show_usage unless ARGV.size == 2
	if filename != ""
	    diff(ARGV[0], ARGV[1],
			    File.new(filename, File::CREAT|File::TRUNC|File::RDWR))
	else
	    diff(ARGV[0], ARGV[1])
	end
	exit
    when "create"
	show_usage unless ARGV.size == 2
	if filename != ""
	    create(ARGV[0], ARGV[1],
			    File.new(filename, File::CREAT|File::TRUNC|File::RDWR))
	else
	    create(ARGV[0], ARGV[1])
	end
	exit
	
    when "update"
	show_usage unless ARGV.size == 2
	update(ARGV[0], ARGV[1])
	exit
    else
	show_usage
    end
end
----------- следующая часть -----------
Было удалено вложение не в текстовом формате...
Имя     : =?iso-8859-1?q?=CF=D4=D3=D5=D4=D3=D4=D7=D5=C5=D4?=
Тип     : application/pgp-signature
Размер  : 189 байтов
Описание: =?iso-8859-1?q?=CF=D4=D3=D5=D4=D3=D4=D7=D5=C5=D4?=
Url     : <http://lists.altlinux.org/pipermail/devel/attachments/20040318/ca35d6d1/attachment-0001.bin>


Подробная информация о списке рассылки Devel